/** * Simple 2d knockback with unit collision detection. * By muzzel */ package Knockback import Terrain import LinkedList @configurable constant string SFX_EARTH = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl" @configurable constant string SFX_WATER = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl" @configurable constant string SFX_ATTACHPOINT = "origin" /** Filter for unit collision. No collision action will be executed for units where this function returns false. */ @configurable function unitValid(unit u) returns boolean return not IsUnitType(u, UNIT_TYPE_DEAD) and (GetUnitTypeId(u) != DUMMY_UNIT_ID) public interface UnitCollision /** * Action that is run for every collision. * unit knockbackUnit: unit currenly performing the knockback * unit hitUnit: unit the knockbackUnit collided with */ function onCollide(unit knockbackUnit, unit hitUnit) public class Knockback use Periodic unit u vec2 acc vec2 vel int steps boolean useEffects boolean stopImpassable effect sfx int sfxCurr UnitCollision collisionAction LinkedList collisionUnits real collisionSize /** * Performs a simple 2d knockback. The knockback object is automatically destroyed afterwards. * unit u: the unit to be knocked back. * vec2 moveVec: defines the knockback distance and direction. * real time: specifies the duration of the knockback. * boolean useEffects: if true, if special effects should be displayed for knockbacks on the ground or shallow water. * boolean stopImpassable: if true, the knockback is stopped if the unit is about to enter impassable terrain. */ construct(unit u, vec2 moveVec, real time, boolean useEffects, boolean stopImpassable) let speed = 2 * moveVec.length() / ((time / ANIMATION_PERIOD) + 1) let acceleration = speed / (time / ANIMATION_PERIOD) let dir = moveVec.norm() this.u = u this.acc = dir * acceleration this.vel = dir * speed this.steps = (time / ANIMATION_PERIOD).toInt() this.useEffects = useEffects this.stopImpassable = stopImpassable this.collisionAction = null if useEffects if isTerrainLand(u.getX(), u.getY()) sfx = u.addEffect(SFX_EARTH, SFX_ATTACHPOINT) sfxCurr = 1 else if isTerrainShallowWater(u.getX(), u.getY()) sfx = u.addEffect(SFX_WATER, SFX_ATTACHPOINT) sfxCurr = 2 startPeriodic(ANIMATION_PERIOD) /** * Enables/disable collision for this knockback. Each unit can only collide once with each knockback. * You can call this directly after creating a new Knockback object or at any other time. * real size: range for collision checks, must be > 0. * UnitCollision action: action that is executed for each collision. Use null to disable collision. */ function enableCollision(real size, UnitCollision action) if action == null // disable if this.collisionAction != null destroy this.collisionAction this.collisionAction = null destroy this.collisionUnits else // enable if this.collisionAction == null this.collisionAction = action this.collisionUnits = new LinkedList() this.collisionSize = size override function periodic() vel -= acc var newPos = u.getPos() + vel // effects: if useEffects if isTerrainLand(u.getX(), u.getY()) and sfxCurr != 1 sfx.destr() sfx = u.addEffect(SFX_EARTH, SFX_ATTACHPOINT) sfxCurr = 1 else if isTerrainShallowWater(u.getX(), u.getY()) and sfxCurr != 2 sfx.destr() sfx = u.addEffect(SFX_WATER, SFX_ATTACHPOINT) sfxCurr = 2 // terrain walkable check: if isTerrainWalkable(newPos.x, newPos.y) u.setXY(newPos) else if stopImpassable destroy this return // check for unit collisions if collisionAction != null if collisionSize > 0. ENUM_GROUP.clear() ENUM_GROUP.enumUnitsInRange(u.getPos(), collisionSize) for unit cu in ENUM_GROUP if unitValid(cu) and cu != u if not collisionUnits.contains(cu) collisionUnits.add(cu) collisionAction.onCollide(u, cu) // destroy if last iteration: steps-- if steps <= 0 destroy this ondestroy if sfx != null sfx.destr() if collisionUnits != null destroy collisionUnits collisionUnits = null