Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 135 additions & 9 deletions GameplayScripts/commons/skills.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from lview import *
import math, itertools, time
from . import items
from commons.damage_calculator import DamageSpecification
from commons.damage_calculator import DamageType

Version = "experimental version"
MissileToSpell = {}
Spells = {}
ChampionSpells = {}

damageCalc = DamageSpecification()
damageType = DamageType.Normal

class SFlag:
Targeted = 1
Line = 2
Expand Down Expand Up @@ -38,6 +43,50 @@ def __init__(self, name, missile_names, flags, delay = 0.0):
name = "?"
missiles = []

#Based on level, not sure exact formula so hardcoded it
AzirSoldierDamage = {
1: 50,
2: 52,
3: 54,
4: 56,
5: 58,
6: 60,
7: 62,
8: 65,
9: 70,
10: 75,
11: 80,
12: 90,
13: 100,
14: 110,
15: 120,
16: 130,
17: 140,
18: 150
}

#Based on level, not sure exact formula so hardcoded it
CassiopeiaEDamage = {
1: 52,
2: 56,
3: 60,
4: 64,
5: 68,
6: 72,
7: 76,
8: 80,
9: 84,
10: 88,
11: 92,
12: 96,
13: 100,
14: 104,
15: 108,
16: 112,
17: 116,
18: 120
}

ChampionSpells = {
"aatrox": [
Spell("aatroxw", ["aatroxw"], SFlag.CollideGeneric)
Expand All @@ -63,6 +112,9 @@ def __init__(self, name, missile_names, flags, delay = 0.0):
Spell("caitlynyordletrap", [], SFlag.Area),
Spell("caitlynentrapment", ["caitlynentrapmentmissile"], SFlag.SkillshotLine)
],
"cassiopeia": [
Spell("cassiopeiaq", [], SFlag.Area, delay = 0.250)
],
"chogath": [
Spell("rupture", [], SFlag.Area, delay = 0.627),
Spell("feralscream", [], SFlag.Cone | SFlag.CollideWindwall)
Expand Down Expand Up @@ -298,22 +350,97 @@ def is_skillshot_cone(skill_name):
if skill_name not in Spells:
return False
return Spells[skill_name].flags & SFlag.Cone

def is_last_hitable(game, player, enemy):
missile_speed = player.basic_missile_speed + 1

def is_soldier_alive(game):
if game.player.name == "azir":
for obj in game.others:
if not obj.is_alive or obj.is_enemy_to(game.player):
continue

if obj.has_tags(UnitTag.Unit_Special_AzirW):
return obj

hit_dmg = items.get_onhit_physical(player, enemy) + items.get_onhit_magical(player, enemy)
return None

def soldier_near_obj(game, enemy):
if game.player.name == "azir":
soldier_affect_range = 650.0
soldier_radius = 325.0

for obj in game.others:
if not obj.is_alive or obj.is_enemy_to(game.player) or game.distance(game.player, obj) > soldier_affect_range + soldier_radius:
continue

if obj.has_tags(UnitTag.Unit_Special_AzirW):
if (game.distance(obj, enemy) < soldier_radius):
return obj

return None

def count_soldiers_near_obj(game, enemy):
num_soldiers = 0
if game.player.name == "azir":
soldier_affect_range = 650.0
soldier_radius = 325.0

for obj in game.others:
if not obj.is_alive or obj.is_enemy_to(game.player) or game.distance(game.player, obj) > soldier_affect_range + soldier_radius:
continue

if obj.has_tags(UnitTag.Unit_Special_AzirW):
if (game.distance(obj, enemy) < soldier_radius):
num_soldiers += 1

return num_soldiers

def is_last_hitable(game, player, enemy):
missile_speed = player.basic_missile_speed + 1.0
atk_speed = player.base_atk_speed * player.atk_speed_multi

#percent_ad/ap will be situationally helpful for last hitting
#damageCalc.percent_ad = 1.0
#damageCalc.percent_ap = 1.0
damageCalc.damage_type = damageType
damageCalc.base_damage = (player.base_atk + player.bonus_atk) - 0.33

#soldier_near_obj returns None if you're not playing Azir
#1 soldier = 0% additional onhit soldier dmg, 2 soldiers = 25% addtional onhit soldier dmg, 3 = 50%, etc..
#one soldier can deal max 150 + 0.60 percent_ap, two soldiers is (150 + 0.60 percent_ap) * 1.25, three is *1.5, etc...
if game.player.name == "azir":
soldier = soldier_near_obj(game, enemy)

if soldier is not None:
num_soldiers = count_soldiers_near_obj(game, enemy)
#Azir dmg formula
damageCalc.base_damage = AzirSoldierDamage[player.lvl] + (player.ap * 0.60)
#Addtional 25% dmg for each additional soldier (num_soldiers-1)
damageCalc.base_damage = (damageCalc.base_damage + (damageCalc.base_damage*((num_soldiers-1) * 0.25))) - 0.25
damageCalc.damage_type = DamageType.Magic
#Missile speed for soldier autos is weird- it isnt a missile but the soldier spears do have a travel time before dmg is registered, it can be interrupted by issuing another command much like a traditional auto windup.
#Couldn't find a basic_atk_windup for azirsoldier so missile speed is partially based on magic number
atk_speed = player.base_atk_speed * player.atk_speed_multi
missile_speed = (3895.0 * atk_speed/player.base_atk_speed)
elif game.player.name == "cassiopeia":
skillE = getattr(game.player, 'E')

if game.player.mana > 50.0:
damageCalc.base_damage = CassiopeiaEDamage[player.lvl] + (player.ap * 0.10)
damageCalc.damage_type = DamageType.Magic
atk_speed = 0.125
missile_speed = 2500

#TODO: integrate item onhit calculation based on damagetype
hit_dmg = (damageCalc.calculate_damage(player, enemy))
hp = enemy.health
atk_speed = player.base_atk_speed * player.atk_speed_multi
t_until_basic_hits = game.distance(player, enemy)/missile_speed#(missile_speed*atk_speed/player.base_atk_speed)
t_until_basic_hits = game.distance(player, enemy)/missile_speed

#where should we be applying client-server latency to the formula - in orbwalker or here?
for missile in game.missiles:
if missile.dest_id == enemy.id:
src = game.get_obj_by_id(missile.src_id)
if src:
t_until_missile_hits = game.distance(missile, enemy)/(missile.speed + 1)
t_until_missile_hits = game.distance(missile, enemy)/((src.basic_missile_speed + 1.0) - 0.4)#- 1.1 #Using src's basic missile speed is most reliable because different minion types have different missile speeds

if t_until_missile_hits < t_until_basic_hits:
hp -= src.base_atk

Expand Down Expand Up @@ -348,7 +475,6 @@ def castpoint_for_collision(game, spell, caster, target):
target_dir.y = 0.0
if math.isnan(target_dir.z):
target_dir.z = 0.0
#print(f'{target_dir.x} {target_dir.y} {target_dir.z}')

# If the spell is a line we simulate the main missile to get the collision point
if spell_extra.flags & SFlag.Line:
Expand Down
39 changes: 36 additions & 3 deletions GameplayScripts/commons/targeting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from lview import *
from enum import Enum
from commons import skills

class Target(Enum):
ClosestToPlayer = 0
Expand All @@ -12,6 +14,11 @@ class TargetingConfig:
Target.LowestHealth: (lambda player, enemy: enemy.health),
Target.MostFed: (lambda player, enemy: -sum([item.cost for item in enemy.items]))
}
#Necessary to properly determine orbwalking last hits - Azir uses soldiers to harass and last hit. Cass uses E to harass and last hit.
special_targeting_champs = {
"azir": 325.0, #Azir soldier radius
"cassiopeia": 711.0 #Cass E range
}
selected = 0
target_minions = False
target_jungle = False
Expand Down Expand Up @@ -42,12 +49,38 @@ def get_target(self, game, range):
def find_target(self, game, array, range, value_extractor):
target = None
min = 99999999
val = 0
for obj in array:

if not obj.is_alive or not obj.is_visible or obj.is_ally_to(game.player) or (game.distance(game.player, obj) - game.player.gameplay_radius - obj.gameplay_radius) > range:
if not obj.is_alive or not obj.is_visible or obj.is_ally_to(game.player):
continue


range_calc = (game.distance(game.player, obj) - game.player.gameplay_radius - obj.gameplay_radius)

#check if our champ is one of special_orbwalk_champs
if game.player.name in self.special_targeting_champs:
if game.player.name == "azir":
soldier = skills.soldier_near_obj(game, obj)

if soldier is not None:
range_calc = (game.distance(soldier, obj))
if range_calc > self.special_targeting_champs[game.player.name]:
continue
else:
if range_calc > range:
continue
elif game.player.name == "cassiopeia":
skillQ = getattr(game.player, 'Q')
skillE = getattr(game.player, 'E')
useQ = False
#TODO: Move Cass Q range value into a data structure
if skillQ.get_current_cooldown(game.time) == 0.0 and range_calc < 850.0:
useQ = True
pass
if not useQ and (skillE.get_current_cooldown(game.time) > 0 or range_calc > self.special_targeting_champs[game.player.name]):
continue

val = value_extractor(game.player, obj)

if val < min:
min = val
target = obj
Expand Down
122 changes: 122 additions & 0 deletions GameplayScripts/evade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from lview import *
from commons.targeting import TargetingConfig
from time import time
import itertools, math
from commons.skills import *
from copy import copy
from math import *

lview_script_info = {
"script": "Evader",
"author": "https://github.com/bckd00r / bckd00r",
"description": "Evade module with LViewLoL"
}

bound_max = 0

evades = False
evade_min_range = 0

targeting = TargetingConfig()

def clamp_norm_2d(v, n_max):
vx = v.x
vy = v.y
vz = v.z
n = sqrt(pow(vx, float(2)) + pow(vz, float(2)))
f = min(n, n_max) / n
return Vec3(f * vx, vy,f * vz)


def PointOnLineSegment(pt1, pt2, pt, epsilon = 0.001):
if (pt.x - max(pt1.x, pt2.x) > epsilon or
min(pt1.x, pt2.x) - pt.x > epsilon or
pt.y - max(pt1.y, pt2.y) > epsilon or
min(pt1.y, pt2.y) - pt.y > epsilon):
return False
if abs(pt2.x - pt1.x) < epsilon:
return abs(pt1.x - pt.x) < epsilon or abs(pt2.x - pt.x) < epsilon
if abs(pt2.y - pt1.y) < epsilon:
return abs(pt1.y - pt.y) < epsilon or abs(pt2.y - pt.y) < epsilon

x = pt1.x + (pt.y - pt1.y) * (pt2.x - pt1.x) / (pt2.y - pt1.y)
y = pt1.y + (pt.x - pt1.x) * (pt2.y - pt1.y) / (pt2.x - pt1.x)

return abs(pt.x - x) < epsilon or abs(pt.y - y) < epsilon

def isLeft(a, b, c):
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) > 0

def lview_load_cfg(cfg):
global evades, evade_min_range
evades = cfg.get_bool("evades", True)
evade_min_range = cfg.get_float("evade_min_range", 500)

def lview_save_cfg(cfg):
global evades, evade_min_range
cfg.set_bool("evades", evades)
cfg.set_float("evade_min_range", evade_min_range)

def lview_draw_settings(game, ui):
global evades, evade_min_range
ui.separator()
ui.text("Evader (Experimental)")
evades = ui.checkbox("Evade skills", evades)
evade_min_range = ui.dragfloat("Minimum evade range", evade_min_range, 100, 0, 3000)
draw_prediction_info(game, ui)


def evade_skills(game, player):
global targeting, evades, evade_min_range
color = Color.WHITE
for missile in game.missiles:
if missile.is_ally_to(game.player):
continue
spell = get_missile_parent_spell(missile.name)
if not spell:
continue
end_pos = missile.end_pos.clone()
start_pos = missile.start_pos.clone()
curr_pos = missile.pos.clone()
dodge_pos = game.player.pos
impact_pos = None
start_pos.y = game.map.height_at(start_pos.x, start_pos.z) + missile.height
end_pos.y = start_pos.y
curr_pos.y = start_pos.y
p = game.world_to_screen(game.player.pos)
br = game.player.gameplay_radius
direction = Vec3(end_pos.x - start_pos.x, end_pos.y - start_pos.y, end_pos.z - start_pos.y)
pos3 = Vec3(end_pos.x + direction.x * float(1.0), end_pos.y + direction.y, end_pos.z + direction.z * -float(1.0))
pos4 = Vec3(end_pos.x + direction.x * -float(1.0), end_pos.y + direction.y, end_pos.z + direction.z * float(1.0))
direction2 = Vec3(pos3.x - pos4.x, pos3.y - pos4.y, pos3.z - pos4.z)
direction2 = clamp_norm_2d(direction2, br)
direction3 = Vec3(0, 0, 0)
direction3.x = -direction2.x
direction3.y = -direction2.y
direction3.z = -direction2.z
if spell.flags & SFlag.Area:
end_pos.y = game.map.height_at(end_pos.x, end_pos.z)

if PointOnLineSegment(game.world_to_screen(start_pos), game.world_to_screen(end_pos), game.world_to_screen(dodge_pos), br):
r = game.get_spell_info(spell.name).cast_radius
percent_done = missile.start_pos.distance(curr_pos)/missile.start_pos.distance(end_pos)
p.y -= 50
game.draw_button(p, "Evade: ON", Color.GRAY, Color.YELLOW, 100)
if isLeft(game.world_to_screen(start_pos), game.world_to_screen(end_pos), game.world_to_screen(dodge_pos)):
direction4 = direction3
else:
direction4 = direction2
evadePos = Vec3(dodge_pos.x + direction4.x, dodge_pos.y + direction4.y, dodge_pos.z + direction4.z)
old_cpos = game.get_cursor()
game.move_cursor(game.world_to_screen(evadePos))
game.press_right_click()

def lview_update(game, ui):
global evades

player = game.player

if evades:
evade_skills(game, player)


2 changes: 2 additions & 0 deletions GameplayScripts/object_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def draw_game_object(obj, ui, additional_draw = None, set_open=False):

ui.separator()
ui.dragfloat("health", obj.health)
ui.dragfloat("mana", obj.mana)
ui.checkbox("is_alive", obj.is_alive)

ui.separator()
Expand All @@ -91,6 +92,7 @@ def draw_game_object(obj, ui, additional_draw = None, set_open=False):
ui.dragfloat("armour", obj.armour)
ui.dragfloat("magic_resist", obj.magic_resist)
ui.dragfloat("ap", obj.ap)
ui.dragfloat("ability_haste", obj.ability_haste)
ui.dragfloat("crit", obj.crit)
ui.dragfloat("crit_multi", obj.crit_multi)

Expand Down
Loading