package Powernode import Escaper import SetupObject import ChannelAbilityPreset import Assets import BounceTest constant EFFECT_STRING = "DRAM" constant EFFECT_BOUNCE_STRING = "LGRO" constant EFFECT_DIODE_STRING = "LONE" constant EFFECT_TETHER_BOTH_STRING = "LBOT" constant EFFECT_TETHER_AIR_STRING = "BAIR" constant EFFECT_ORB_STRING = "LORB" constant KILL_EFFECT = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareBoltImpact.mdl" constant ACTIVATION_DIST = 22. public constant INCREASE_BOUNCE_ID = compiletime(ABIL_ID_GEN.next()) public constant DECREASE_BOUNCE_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_B_GROUND_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_B_AIR_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_B_BOTH_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_KILL_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_SPELLBOOK_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_DIODE_GROUND_ID = compiletime(ABIL_ID_GEN.next()) public constant LINK_ORB_ID = compiletime(ABIL_ID_GEN.next()) enum TetherType KILL BOUNCE_GROUND BOUNCE_AIR BOUNCE_BOTH BOUNCE_GROUND_DIODE BLOCK_ORB class Link Link next = null Link prev = null TetherType ltype Powernode parent Powernode target lightning sfx construct(Powernode parent, Powernode target, lightning sfx, TetherType ltype) this.parent = parent this.target = target this.sfx = sfx this.ltype = ltype if parent.firstLink != null next = parent.firstLink parent.firstLink.prev = this parent.firstLink = this ondestroy if prev != null prev.next = next if next != null next.prev = prev if parent.firstLink == this parent.firstLink = next DestroyLightning(sfx) public class Powernode extends SetupObject let bounciness = new ConfigValue(0.75, 0.25, "Bounciness") protected Link firstLink = null construct(vec2 pos, player owner) super(createUnit(owner, POWER_NODE_ID, pos, angle(0)), createUnit(owner, POWER_NODE_ID, pos, angle(0)), 0.) setup..addAbility(LINK_SPELLBOOK_ID) ..addAbility(INCREASE_BOUNCE_ID)..addAbility(DECREASE_BOUNCE_ID)..addAbility(TURN_OFF_ID) sleeps = false EventListener.add(setup, EVENT_PLAYER_UNIT_SPELL_CAST, () -> onCast()) function link(Powernode p, TetherType ltype) switch ltype case KILL new Link(this, p, AddLightning(EFFECT_STRING, false, pos.x, pos.y, p.pos.x, p.pos.y), ltype) case BOUNCE_GROUND new Link(this, p, AddLightning(EFFECT_BOUNCE_STRING, false, pos.x, pos.y, p.pos.x, p.pos.y), ltype) case BOUNCE_GROUND_DIODE new Link(this, p, AddLightning(EFFECT_DIODE_STRING, false, pos.x, pos.y, p.pos.x, p.pos.y), ltype) case BOUNCE_BOTH new Link(this, p, AddLightning(EFFECT_TETHER_BOTH_STRING, false, pos.x, pos.y, p.pos.x, p.pos.y), ltype) case BOUNCE_AIR new Link(this, p, AddLightning(EFFECT_TETHER_AIR_STRING, false, pos.x, pos.y, p.pos.x, p.pos.y), ltype) case BLOCK_ORB new Link(this, p, AddLightning(EFFECT_ORB_STRING, false, pos.x, pos.y, p.pos.x, p.pos.y), ltype) override function update() super.update() var link = firstLink while link != null if link.target.done let tmp = link link = link.next destroy tmp else MoveLightning(link.sfx, false, pos.x, pos.y, link.target.pos.x, link.target.pos.y) for Escaper e from this.getCurrentRegion().getEscapers() if link.ltype == TetherType.BLOCK_ORB if e.orb != null and e.orb.pos.toVec2().distanceToSegmentSq(pos.toVec2(), link.target.pos.toVec2()) < ACTIVATION_DIST.squared() flashEffect(Abilities.spellBreakerAttack, e.orb.pos) Log.debug("Orb P Terminate") e.orb.terminate() else if e.alive and e.pos.toVec2().distanceToSegmentSq(pos.toVec2(), link.target.pos.toVec2()) < ACTIVATION_DIST.squared() if e.pos.z < 24 or link.ltype == TetherType.BOUNCE_AIR or link.ltype == TetherType.BOUNCE_BOTH switch link.ltype case KILL AddSpecialEffect(KILL_EFFECT , e.pos.x, e.pos.y).destr() e.kill(this) case BOUNCE_GROUND calBounceSimple(link.target.pos,e, bounciness.get()) case BOUNCE_GROUND_DIODE calBounceDiode(link.target.pos,e, bounciness.get()) case BOUNCE_AIR if e.pos.z > 10 addEffect(Abilities.dispelMagicTarget, e.getPos()).destr() calBounceSimple(link.target.pos,e, bounciness.get()) case BOUNCE_BOTH if e.pos.z > 20 addEffect(Abilities.dispelMagicTarget, e.getPos()).destr() calBounceSimple(link.target.pos,e, bounciness.get()) case BLOCK_ORB link = link.next override function setEnabled(boolean b) super.setEnabled(b) let v = b.toInt().toReal() var l = firstLink while l != null SetLightningColor(l.sfx, v, v, v, v) l = l.next function getNormal(vec2 la, vec2 lb, vec2 pos) returns vec3 let dx = lb.x - la.x let dy = lb.y - la.y if ((lb.x - la.x)*(pos.y - la.y) - (lb.y - la.y)*(pos.x - la.x)) > 0 return vec2(-dy,dx).norm().withZ(0) return vec2(dy,-dx).norm().withZ(0) function calBounceDiode(vec3 lpos, Escaper e, real bounceFactor) let dx = lpos.x - e.pos.x let dy = lpos.y - e.pos.y let normal = vec2(-dy,dx).norm().toVec3() let b = getBounceVec(normal, normal.toVec2(), -bounceFactor) * 3 e.setPos(e.pos+b) e.setVel(b) e.slideVelocity = ZERO2 function calBounceSimple(vec3 lpos, Escaper e, real bounceFactor) let normal = calculateNormal(lpos.toVec2(), pos.toVec2(), e.pos.toVec2()) if e.flying let refVel = reflectVelocity(e.vel.toVec2() + e.walkVel + e.slideVelocity + e.otherVel, normal, bounceFactor) e.setXY(e.pos+normal+refVel) e.setVel(refVel.withZ(e.getVel().z)) else e.setXY(e.pos - e.walkVel - e.slideVelocity + normal * 2) e.slideVelocity = ZERO2 function getBounceVec(vec3 vel3, vec2 nor2, real bounceFactor) returns vec3 let nor3 = nor2.toVec3() let dotProduct = vel3.dot(nor3) // Dot product of velocity and normal var reflection = vel3 - (2 * dotProduct * nor3) // Reflection vector calculation reflection *= bounceFactor // Apply bounce factor to adjust the magnitude return reflection ondestroy var l = firstLink while l != null let tmp = l l = l.next destroy tmp destroy bounciness function onCast() let id = GetSpellAbilityId() let target = GetSpellTargetUnit() let node = GetTriggerUnit().getEntity() castTo Powernode if id == LINK_KILL_ID or id == LINK_B_GROUND_ID or id == LINK_DIODE_GROUND_ID or id == LINK_B_AIR_ID or id == LINK_ORB_ID or id == LINK_B_BOTH_ID let data = target.getEntity() if data instanceof Powernode let pdata = data castTo Powernode var isNew = true var l = node.firstLink while l != null if l.target == pdata destroy l isNew = false break l = l.next if isNew if id == LINK_KILL_ID node.link(data castTo Powernode, TetherType.KILL) else if id == LINK_B_GROUND_ID node.link(data castTo Powernode, TetherType.BOUNCE_GROUND) else if id == LINK_B_BOTH_ID node.link(data castTo Powernode, TetherType.BOUNCE_BOTH) else if id == LINK_DIODE_GROUND_ID node.link(data castTo Powernode, TetherType.BOUNCE_GROUND_DIODE) else if id == LINK_ORB_ID node.link(data castTo Powernode, TetherType.BLOCK_ORB) else if id == LINK_B_AIR_ID node.link(data castTo Powernode, TetherType.BOUNCE_AIR) else if id == INCREASE_BOUNCE_ID node.bounciness.increment(node, 8) var link = node.firstLink while link != null if link.target != this link.target.bounciness.setVal(node.bounciness.get()) link = link.next else if id == DECREASE_BOUNCE_ID node.bounciness.decrement(node, 0) var link = node.firstLink while link != null if link.target != this link.target.bounciness.setVal(node.bounciness.get()) link = link.next else if id == TURN_ON_ID setEnabled(true) setup.abortOrder() else if id == TURN_OFF_ID setEnabled(false) setup.abortOrder() override function serialize() returns Json let json = super.serialize() json.addProperty(new Property(KEY_TYPE, POWERNODE_INDEX.toString())) return json @objectgen function genPowerNode() new UnitDefinition(POWER_NODE_ID, 'hfoo') ..setAttacksEnabled(0) ..setTargetedAs("ground") ..setAnimationBlendTimeseconds(0) ..setAnimationCastBackswing(0) ..setIconGameInterface("ReplaceableTextures\\CommandButtons\\BTNLightningShield.blp") ..setButtonPositionX(3) ..setButtonPositionY(1) ..setModelFile("units\\orc\\WatcherWard\\WatcherWard.mdl") ..setTintingColorBlue(200) ..setTintingColorGreen(100) ..setTintingColorRed(100) ..setSelectionScale(0.2) ..setSpeedBase(180) ..setTurnRate(3) ..setMovementType(MovementType.Foot) ..setUnitSoundSet("") ..setCollisionSize(0) ..setShadowImageUnit("") ..setNormalAbilities(commaList('A01H', 'A01E', REMOVE_OBJECT_ID, GHOST_VIS_ID, INVULNERABILITY_ID)) ..setHotkey("F") ..setName("|cff9CA5EFPower Node") ..setTooltipBasic("|cffFFCC00Build |cff9CA5EFPower Node [F]") ..setTooltipExtended("Can be linked to other Powernodes to create an electrical circuit." + "\nThe resulting lightning kills Escapers if they try to pass it.") ..setUpgradesUsed("") ..setBuildTime(1) ..setUnitClassification("ancient") ..setHitPointsMaximumBase(100000000) ..setSightRadiusDay(0) ..setSightRadiusNight(0) ..setFoodCost(0) ..setGoldCost(0) ..setLumberCost(0) ..setIsaBuilding(false) ..setHideMinimapDisplay(true) new ChannelAbilityPreset(INCREASE_BOUNCE_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Increase Bounciness") ..presetIcon("ReplaceableTextures\\CommandButtons\\BTNReplay-Speedup.blp") ..presetHotkey("C") ..presetTooltipNormal(lvl -> "Increase Bounciness [|cffFFCC00C|r]") ..presetTooltipNormalExtended(lvl -> "") ..presetButtonPosNormal(2, 2) new ChannelAbilityPreset(DECREASE_BOUNCE_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Decrease Bounciness") ..presetIcon("ReplaceableTextures\\CommandButtons\\BTNReplay-Speeddown.blp") ..presetHotkey("D") ..presetTooltipNormal(lvl -> "Decrease Bounciness [|cffFFCC00D|r]") ..presetTooltipNormalExtended(lvl -> "") ..presetButtonPosNormal(2, 1) new ChannelAbilityPreset(LINK_B_AIR_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Link |cffFFCC00Block Air|r") ..presetIcon("BTNStasisTrap.blp") ..presetHotkey("S") ..presetTooltipNormal(lvl -> "Add/Remove |cffFFCC00Block Air| [|cffFFCC00S|r]") ..presetTooltipNormalExtended(lvl -> "Add or remove this type of link from another powernode.\n" + "Blocks escapers only when they are in air.") ..presetButtonPosNormal(1, 1) ..presetTargetTypes(Targettype.UNIT) ..presetCastRange(lvl -> 2500) ..presetTargetsAllowed(lvl -> "allies,friend,invulnerable,notself,vulnerable") new ChannelAbilityPreset(LINK_KILL_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Link |cffFFCC00Kill Ground|r") ..presetIcon("BTNMonsoon.blp") ..presetHotkey("R") ..presetTooltipNormal(lvl -> "Add/Remove Link [|cffFFCC00R|r]") ..presetTooltipNormalExtended(lvl -> "Add or remove this type of link from another powernode.\n" + "Kills escapers in proximity when they are |cffFFCC00on the ground|r.") ..presetButtonPosNormal(3, 0) ..presetTargetTypes(Targettype.UNIT) ..presetCastRange(lvl -> 2500) ..presetTargetsAllowed(lvl -> "allies,friend,invulnerable,notself,vulnerable") new ChannelAbilityPreset(LINK_B_GROUND_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Link |cffFFCC00Block Ground|r") ..presetIcon("BTNHealingWave.blp") ..presetHotkey("Q") ..presetTooltipNormal(lvl -> "Add/Remove Link [|cffFFCC00Q|r]") ..presetTooltipNormalExtended(lvl -> "Add or remove this type of link from another powernode.\n" + "Blocks escapers only when they are |cffFFCC00on the ground|r.") ..presetButtonPosNormal(0, 0) ..presetTargetTypes(Targettype.UNIT) ..presetCastRange(lvl -> 2500) ..presetTargetsAllowed(lvl -> "allies,friend,invulnerable,notself,vulnerable") new ChannelAbilityPreset(LINK_DIODE_GROUND_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Link Diode") ..presetIcon("BTNBanish.blp") ..presetHotkey("E") ..presetTooltipNormal(lvl -> "Add/Remove |cffFFCC00Diode Ground|r [|cffFFCC00E|r]") ..presetTooltipNormalExtended(lvl -> "Add or remove this type of link from another powernode.\n" + "|cffFFCC00Blocks|r escapers coming from one side, but let's them |cffFFCC00pass|r from the other.") ..presetButtonPosNormal(2, 0) ..presetTargetTypes(Targettype.UNIT) ..presetCastRange(lvl -> 2500) ..presetTargetsAllowed(lvl -> "allies,friend,invulnerable,notself,vulnerable") new ChannelAbilityPreset(LINK_ORB_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Link Orb") ..presetIcon("BTNMageOrb.blp") ..presetHotkey("A") ..presetTooltipNormal(lvl -> "Add/Remove |cffFFCC00Block Orb|r [|cffFFCC00A|r]") ..presetTooltipNormalExtended(lvl -> "Add or remove this type of link from another powernode.\n" + "Blocks any |cffFFCC00on orb missile|r casted by an escaper.\nThe orb ability can be activated in your region setup (yellow box).") ..presetButtonPosNormal(0, 1) ..presetTargetTypes(Targettype.UNIT) ..presetCastRange(lvl -> 2500) ..presetTargetsAllowed(lvl -> "allies,friend,invulnerable,notself,vulnerable") new ChannelAbilityPreset(LINK_B_BOTH_ID, 1, true) ..presetManaCost(lvl -> 0) ..setHeroAbility(false) ..presetCooldown(lvl -> 0) ..setName("Link Both") ..presetIcon("BTNEnsnare.blp") ..presetHotkey("W") ..presetTooltipNormal(lvl -> "Add/Remove |cffFFCC00Block Both|r [|cffFFCC00W|r]") ..presetTooltipNormalExtended(lvl -> "Add or remove this type of link from another powernode.\n" + "Blocks escaper |cffFFCC00on the ground and in the air|r.") ..presetButtonPosNormal(2, 0) ..presetTargetTypes(Targettype.UNIT) ..presetCastRange(lvl -> 2500) ..presetTargetsAllowed(lvl -> "allies,friend,invulnerable,notself,vulnerable") new AbilityDefinitionSpellBook(LINK_SPELLBOOK_ID) ..setMaximumSpells(1, 6) ..setMinimumSpells(1, 6) ..setBaseOrderID(1, "spellbook") ..setSpellList(1, commaList(LINK_B_GROUND_ID, LINK_B_BOTH_ID, LINK_DIODE_GROUND_ID, LINK_KILL_ID, LINK_ORB_ID, LINK_B_AIR_ID)) ..setSharedSpellCooldown(1, false) ..setIconNormal("ReplaceableTextures\\CommandButtons\\BTNEnchantedGemstone.blp") ..setTooltipNormal(1, "Setup |cffFFCC00Tether [|cffFFCC00R|r]") ..setTooltipNormalExtended(1, "Allows you to link this node with another.") ..presetHotkey("R") ..presetButtonPosNormal(3, 0) ..setEditorSuffix("(Wizard)") ..setName("Powernode: Setup Tether") ..setItemAbility(false)