• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Life Steal V2 Remake

This bundle is marked as awaiting update. A staff member has requested changes to it before it can be approved.
Create five ghosts that go into nearby enemy units. If enemy unit dies, a Ghoul will be created for last 30 seconds. After Gould died the ghosts will return heal to the caster.
-------------------
Wurst:
package LifeSteal

import ChannelAbilityPreset
import HashMap
import LinkedList
import InstantDummyCaster
import ClosureEvents
import UnitIds
import OrderIds
import AbilityIds
import ClosureForGroups

public constant LIFE_STEAL_ID = compiletime(ABIL_ID_GEN.next())

constant GHOST_UNIT_ID = compiletime(UNIT_ID_GEN.next())

constant SHADOW_STRIKE_ID = compiletime(ABIL_ID_GEN.next())
constant FINGER_OF_DEATH_ID = compiletime(ABIL_ID_GEN.next())

constant INFECTED_UNITS = new HashMap<unit, LinkedList<unit>>
constant INFECTION_GHOULS = new HashMap<unit, unit>
constant INFECTION_GHOSTS = new HashMap<unit, unit>

@configurable public boolean LIFE_STEAL_MULTIPLE_INFECTION_PER_CASTER = true

@configurable public real LIFE_STEAL_GHOUL_LIFESPAN = 10
@configurable public int  LIFE_STEAL_GHOUL_UNIT_ID = 'ugho'

@configurable public real LIFE_STEAL_GHOST_LIFESPAN = 30
@configurable public int  LIFE_STEAL_GHOST_SPEED = 400
@configurable public int  LIFE_STEAL_GHOST_RETURN_HP = 100

@configurable public real LIFE_STEAL_CD = 2
@configurable public real LIFE_STEAL_RADIUS = 500
@configurable public real LIFE_STEAL_INITIAL_DAMAGE = 200
@configurable public real LIFE_STEAL_DPS_VALUE = 150
@configurable public real LIFE_STEAL_DPS_DURATION = 5
@configurable public int  LIFE_STEAL_MANA_COST = 50
@configurable public int  LIFE_STEAL_GHOSTS = 5

@compiletime function genLifeSteal()
    new AbilityDefinitionMountainKingThunderClap(LIFE_STEAL_ID)
        ..setArtCaster("")
        ..setArtEffect("")
        ..presetIcon("BTNMaskOfDeath")
        ..setHotkeyNormal("F")
        ..setLevels(1)
        ..setCooldown(1, LIFE_STEAL_CD)
        ..setBuffs(1, "")
        ..setMovementSpeedReduction(1, 0)
        ..setAttackSpeedReduction(1, 0)
        ..setAOEDamage(1, 0)
        ..setManaCost(1, LIFE_STEAL_MANA_COST)
        ..setDurationHero(1, 0)
        ..setDurationNormal(1, 0)
        ..setName("Life Steal")
        ..presetTooltipNormal(lvl -> "Li|cffffcc00f|re Steal - Level " + lvl.toString())
        ..presetTooltipNormalExtended(lvl -> "Create five spirit that go into nearby enemy units. Damage enemies and reduce their movement speed. If enemy unit dies, a Ghoul will be created for last 30 seconds. After Gould died the spirit will return to heal the caster. Also increase miss chance for nearby enemies.")

@compiletime function genShadowStrike()
    new AbilityDefinitionWardenShadowStrike(SHADOW_STRIKE_ID)
        ..setDummyAbility()
        ..setDurationNormal(1, LIFE_STEAL_DPS_DURATION)
        ..setDecayingDamage(1, LIFE_STEAL_DPS_VALUE)
        ..setInitialDamage(1, LIFE_STEAL_INITIAL_DAMAGE)

@compiletime function genFingerOfDeath()
    new AbilityDefinitionFingerofDeath(FINGER_OF_DEATH_ID)
        ..setDummyAbility()
        ..setDamage(1, 0)
        ..setTargetsAllowed(1, "ground, air, friend, enemy")
        ..setCastRange(1, 100)

@compiletime function genGhost()
    new UnitDefinition(GHOST_UNIT_ID, UnitIds.peasant)
        ..setModelFile("HeroDreadLord_Ghost.mdx")
        ..setDeathType(0)
        ..setSpeedBase(LIFE_STEAL_GHOST_SPEED)
        ..setNormalAbilities(commaList(AbilityIds.invulnerable, AbilityIds.locust, FINGER_OF_DEATH_ID))
        ..setCanFlee(false)
        ..setFoodCost(0)
        ..setHideMinimapDisplay(true)
        ..setUpgradesUsed("")
        ..setAttacksEnabled(0)
        ..setArtSpecial("")

function isAlreadyInfectedByCaster(unit caster, unit target) returns boolean 
    if INFECTED_UNITS.has(target) == false or INFECTED_UNITS.get(target).has(caster) == false
        return false

    return true

function isInfectedByAny(unit unitToCheck) returns boolean
    return INFECTED_UNITS.has(unitToCheck)

function isInfectionGhoul(unit unitToCheck) returns boolean
    return INFECTION_GHOULS.has(unitToCheck)

function addToInfectedUnits(unit caster, unit target)
    if isInfectedByAny(target) == false
        INFECTED_UNITS.put(target, new LinkedList<unit>)
 
    LinkedList<unit> casters = getInfectorsByInfected(target)
    casters.add(caster)

function removeFromInfectedUnits(unit infectedUnit)
    INFECTED_UNITS.remove(infectedUnit)

function addToInfectionGhouls(unit caster, unit ghoul)
    INFECTION_GHOULS.put(ghoul, caster)

function addToInfectionGhosts(unit caster, unit ghost)
    INFECTION_GHOSTS.put(ghost, caster)
 
function removeFromInfectionGhouls(unit ghoul)
    INFECTION_GHOULS.remove(ghoul)

function removeFromInfectionGhosts(unit ghost)
    INFECTION_GHOSTS.remove(ghost)

function getInfectorsByInfected(unit infected) returns LinkedList<unit>
    return INFECTED_UNITS.get(infected)
 
function getInfectorByGhoul(unit ghoul) returns unit
    return INFECTION_GHOULS.get(ghoul)

function getInfectorByGhost(unit ghost) returns unit
    return INFECTION_GHOSTS.get(ghost)
 
function createGhoul(player owner, vec2 position) returns unit
    unit ghoul = CreateUnit(owner, LIFE_STEAL_GHOUL_UNIT_ID, position.x, position.y, bj_UNIT_FACING)
    ghoul.setTimedLife(LIFE_STEAL_GHOUL_LIFESPAN)
    return ghoul

function createGhost(player owner, vec2 position) returns unit
    unit ghost = CreateUnit(owner, GHOST_UNIT_ID, position.x, position.y, bj_UNIT_FACING)
    ghost.setTimedLife(LIFE_STEAL_GHOST_LIFESPAN)
    return ghost
 

function handleInfectedUnitDeath(unit deadUnit)
    vec2 deadUnitPosition = deadUnit.getPos()
    LinkedList<unit> infectors = getInfectorsByInfected(deadUnit)
    infectors.forEach() (unit infector) ->
        unit ghoul = createGhoul(infector.getOwner(), deadUnitPosition)
        addToInfectionGhouls(infector, ghoul)

        removeFromInfectedUnits(deadUnit)

function handleInfectionGhoulDeath(unit deadGhoul)
    unit caster = getInfectorByGhoul(deadGhoul)
    removeFromInfectionGhouls(deadGhoul)
    if caster.isAlive() == false
        return

    unit returnGhost = createGhost(caster.getOwner(), deadGhoul.getPos())
    returnGhost.issueTargetOrder("fingerofdeath", caster)

function isUnitAcceptableAsTarget(unit caster, unit target) returns boolean
    if caster.isEnemyOf(target.getOwner()) == false
        return false
 
    if target.isInvulnerable()
        return false
 
    if target.isHidden()
        return false

    if target.isIllusion()
        return false
 
    if target.isSelectable() == false
        return false

    if LIFE_STEAL_MULTIPLE_INFECTION_PER_CASTER == false
        if isAlreadyInfectedByCaster(caster, target)
            return false
 
    return true

init
    EventListener.add(EVENT_PLAYER_UNIT_DEATH) () ->
        unit deadUnit = GetTriggerUnit()

        if isInfectedByAny(deadUnit)
            handleInfectedUnitDeath(deadUnit)

        if isInfectionGhoul(deadUnit)
            handleInfectionGhoulDeath(deadUnit)
 
    EventListener.onTargetCast(FINGER_OF_DEATH_ID) (unit ghost, unit target) ->
        if ghost.isEnemyOf(target.getOwner())
            addToInfectedUnits(getInfectorByGhost(ghost), target)
            removeFromInfectionGhosts(ghost)
            InstantDummyCaster.castTarget(ghost.getOwner(), SHADOW_STRIKE_ID, 1, OrderIds.shadowstrike, target)
        else
            AddSpecialEffectTarget("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl", target, "origin")
            target.setHP(target.getHP() + LIFE_STEAL_GHOST_RETURN_HP)

        ghost.kill()
 
    EventListener.onCast(LIFE_STEAL_ID) (unit caster) ->
        LinkedList<unit> allUnitsAround = new LinkedList<unit>
        forUnitsInRange(caster.getPos(), LIFE_STEAL_RADIUS) (unit u) ->
            allUnitsAround.add(u)
 
        LinkedList<unit> acceptableUnits = allUnitsAround.filter() (unit target) ->
            return isUnitAcceptableAsTarget(caster, target)
 
        acceptableUnits.shuffle()
        int ghotsCount = LIFE_STEAL_GHOSTS < acceptableUnits.size() ? LIFE_STEAL_GHOSTS : acceptableUnits.size()
        if ghotsCount > 0
            for i = 0 to (ghotsCount - 1)
                unit target = acceptableUnits.pop()
                unit ghost = createGhost(caster.getOwner(), caster.getPos())
                addToInfectionGhosts(caster, ghost)
                ghost.issueTargetOrder("fingerofdeath", target)
-------------------

Also use this repo to install it as a dependency:
omidh28/LifeSteal-Warcraft3

-------------------

What Changed?

Well It's basically the same spell made 9 years ago but rewritten to be MUI, more configurable and in Wurst instead of triggers
Keywords:
Life Steal,Dread lord, Spirit, Ghost
Contents

Untitled (Map)

Reviews
MyPad
I do not specialize in Wurst, so my thoughts on certain matters regarding its semantics may bear little weight. Code Split compile-time related functions from run-time functions. Configurable variables should be stored in another file, annotated...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

29 Aug 2011
Your triggers are a mess. I highly recommend asking for help on simplifying your triggers, make a post in the Triggers & Scripts forum here: http://www.hiveworkshop.com/forums/triggers-scripts-269/


Maker, Life Steal V1.1, 5th Sep 2011

LS1 leaks a location when you set the group.
Some dummy displays worker icon.
The dummies are not removed from the game.
You should turn off triggers when there are no instances of this spell active.
You're setting target unit of ability being cast twice in LS2.
You have a If/Then/Else without a condition in LS3.
Add importing instructions into the map.
 
Level 20
Joined
Jul 12, 2010
Messages
1,717
Can you explain better?

MUI spells are the spells that can be casted by multiple units at the same time (i think xD)

anyway the spell i good and also it's unique, i like that !!
but you should edit the buffs...
this is not going to be approved if it's not MUI...
also i can see that it has leaks...
but the idea is good and the spell works (with 1 unit only) so ill give you 5/5 :thumbs_up:
 
Level 12
Joined
Apr 4, 2010
Messages
862
Gonna test it.

Edit: Read the rules before uploading, this spell is not MUI.

MUI spells are the spells that can be casted by multiple units at the same time (i think xD)

anyway the spell i good and also it's unique, i like that !!
but you should edit the buffs...
this is not going to be approved if it's not MUI...
also i can see that it has leaks...
but the idea is good and the spell works (with 1 unit only) so ill give you 5/5 :thumbs_up:

Spell not being MUI will only give a REJECTION and 2/5.
 
I do not specialize in Wurst, so my thoughts on certain matters regarding its semantics may bear little weight.

Code

  • Split compile-time related functions from run-time functions.
  • Configurable variables should be stored in another file, annotated with "_config".
  • Boolean comparisons in the code can be simplified to the following form:

    • Lua:
      INFECTED_UNITS.has(target) == true
      ->
      Lua:
      INFECTED_UNITS.has(target)
  • Likewise,

    • Lua:
      INFECTED_UNITS.has(target) == false
      ->
      Lua:
      not INFECTED_UNITS.has(target)
      or an equivalent negation unary operator.
  • The line AddSpecialEffectTarget("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl", target, "origin") appears to have a hardcoded model path. If this is not intended, store the path into a configurable variable.

Spell:

  • Unless desired, avoid terrain deformations when creating custom spells based on them. Use other base spells like Berserk, Wind Walk for the same purpose.

  • Improve tooltip description. Correct misspelled words and include more information on side effects.
Ultimately, most of the Wurst semantics can be further inspected by @Frotty or @Cokemonkey11. From run-time tests, the spell appears to work sufficiently well enough for a reconsideration on the previous review.

Moving to Awaiting Update
 
Top