- Joined
- May 9, 2014
- Messages
- 1,819
Sales Pitch:
This library abstracts away all of the tedious implementation details of custom stats while still leaving you, the user, with the ability to define the overall behavior for each custom stat. Want to make a Lifesteal stat? Sure thing, just hook it up to the system and handle the
rest (damage event from an attack). What about Healing Effects? Bonus Attack Damage? Flat Movement Speed? Bonus Movement Speed? This system can handle all of those handily. All you have to do is define their behavior.
JASS:
library CStatFactory requires /*
-----------------------
*/ optional Init /*
-----------------------
You can fetch a ready-made module initializer library from the link below:
- "https://github.com/SinisterVLuffy/NestharusJASS/blob/master/jass/Systems/Init/script.j"
--------------
*/ Alloc /*
--------------
Written by AGD, this would do well for the allocation of effectively unlimited instances.
- "https://www.hiveworkshop.com/threads/global-alloc.324937/"
--------------
*/ ListT /*
--------------
The one by Bannar is most preferred:
- https://www.hiveworkshop.com/threads/containers-list-t.249011/#post-3271599
------------------
*/ UnitDex /*
------------------
Written by TriggerHappy. Make sure that the Detect Leave ability is copied into your map
- https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
--------------
*/ Table /*
--------------
Written by Bribe. A staple for a lot of vJASS resources.
- https://www.hiveworkshop.com/threads/lua-vjass-new-table.188084/page-8#post-3448830
---------------------------
*/ optional Bitwise /*
---------------------------
Written by Bannar, with significant contributions from Nestharus and Magtheridon.
Due to this system's use of bitwise operations which older versions (1.30 and below)
of wc3 do not offer directly, this optional dependency is needed to bridge that gap
in features.
- "https://www.hiveworkshop.com/threads/snippet-bitwise.249223/"
---------------------------------------------------------------------------
|
| Custom Unit Stat Factory:
| v.1.0
| MyPad
|
|---------------------------------------------------------------------------
|
| This library offers a straightforward implementation of custom unit stats, as
| well as any other bonuses (bonii) offered. While initially designed for
| float-based custom stats, this version offers int-based custom stats as well.
|
| This library approaches the problem of dealing with multiple modifier instances
| in a manner that treats them as struct instances themselves. Thus, applying and revoking
| bonuses for custom unit stats comes as naturally as .create() and .destroy() does
| for structs.
|
| The removal of modifier instances is handled automatically via
| the event OnUnitDeindex (provided by UnitDex). Hence, there's little to no need
| to worry about removing them manually upon the removal of units affected by
| said modifier instances.
|
|---------------------------------------------------------------------------
|
| External API:
|
| class CStat
| {
| method operator real= -> real
| method operator amount= -> real
| - Returns the float value of the modifier instance.
| - Also permits the changing of the float value to your desired value.
|
| method operator int= -> integer
| - The numerator value (default: 0)
| method operator divisor= -> integer
| - The denominator value (default: 1)
| - Note: The equivalent set operation never allows a value of 0.
|
| method operator owner -> unit
| - The unit the bonus instance acts upon.
| method operator modClass -> integer
| - The class ID of the instance.
| - Trying to get the class ID of the instance via .typeid will only return the
| class ID of CStat.
| method operator statType -> integer
| - Describes whether the modifier instance is based on a float-based custom stat
| or an int-based custom stat.
| - Enum Values:
| - CSTAT_TYPE_FLOAT : 1 (Default)
| - CSTAT_TYPE_INT : 2
|
| method isActive() -> boolean
| - Returns true if the modifier instance is active.
| method activate(boolean flag)
| - Increments or decrements the active counter of the modifier instance based on
| the flag value. (True activates, False deactivates)
| }
|
| modules:
|
| When implementing these modules, it's imperative to define methods predicated
| with the "virtual" keyword. Those prefixed with "stub" are optionally defined.
|
| module CUnitStatFactoryHeader
| {
| ==============================================
| Overrideable
| ==============================================
| virtual static constant method STAT_TYPE() -> int
| - This determines whether the custom stat is float-based or int-based/
| - Enum values:
| - CSTAT_TYPE_FLOAT (Default)
| - CSTAT_TYPE_INT
|
| stub method onApply(unit whichUnit, real amount)
| - Runs whenever a modifier instance's (real/float) value is modified
| via .real= or .amount=
|
| stub method onApplyInt(unit whichUnit, integer amount)
| - Runs whenever a modifier instance's (integer) value is modified
| via .int= or .divisor=
|
| stub method onBonusCalc(unit whichUnit, real base, real sum,
| real product, boolean zeroMultiple) -> real
| - Returns the final value of the specified custom stat
| for the unit (after calculations).
| - Used immediately in the method .onApply()
|
| stub method onBonusCalcInt(unit whichUnit, integer base, integer sum,
| integer product, boolean zeroMultiple) -> integer
| - Same as .onBonusCalc() but for integers.
|
| ==============================================
| Non-overrideable
| ==============================================
| static method apply(unit whichUnit, real value, integer modType) -> CStat
| - The function which defines this system.
| - Applies a modifier instance onto a unit. The class ID of
| the instance depends on the class/struct calling this method.
| - Fails when the unit isn't registered.
| - Units are registered automatically to the system by default.
| Hence, the scenario above wouldn't likely occur.
| - Valid values for modType:
| - STAT_ADD
| - STAT_MULT
|
| static method applyInt(unit whichUnit, int value, integer modType) -> CStat
| - Same as apply for int-based custom stats.
| - Valid values for modType:
| - STAT_ADD
| - STAT_MULT
|
| static method getProduct(unit whichUnit) -> real
| - Returns the aggregated product of all nonzero multiplicative modifiers of
| the unit's custom stat.
|
| static method getSum(unit whichUnit) -> real
| - Returns the aggregated sum of all additive modifiers of
| the unit's custom stat.
|
| static method getBaseValue(unit whichUnit) -> real
| - Returns the unit's base custom stat.
|
| static method getTotalValue(unit whichUnit) -> real
| - Returns the unit's custom stat value after modifiers.
|
| static method setBaseValueEx(unit whichUnit, real value, boolean silentUpdate)
| - Updates the unit's base custom stat to the specified value.
| - If silentUpdate is true, this will not update the unit's current
| custom stat value.
|
| static method setBaseValue(unit whichUnit, real value)
| - A wrapper method for setBaseValueEx(whichUnit, value, silentUpdate)
| with false as the parameter for "silentUpdate"
|
| static method getIntProduct(unit whichUnit) -> integer
| static method getIntSum(unit whichUnit) -> integer
| static method getIntBaseValue(unit whichUnit) -> integer
| static method getIntTotalValue(unit whichUnit) -> integer
| static method setIntBaseValueEx(unit whichUnit, integer value, boolean silentUpdate)
| static method setIntBaseValue(unit whichUnit, integer value)
| - All integer equivalents of the above operations.
| }
|
| module CUnitStatFactoryFooter
| {
| ==============================================
| Defineable
| ==============================================
| stub static method onRegister(unit whichUnit)
| - Runs whenever a unit has been registered to the system.
| - By default, this runs whenever a unit is created.
| - You can specify which units to register via .onFilterRegister(whichUnit).
|
| stub static method onUnregister(unit whichUnit)
| - Runs whenever a registered unit has left the game
| (either through decay, RemoveUnit or ExlodeUnit).
|
| stub static method onFilterRegister(unit whichUnit) -> boolean
| - Runs whenever a unit is created.
| - Return true to register the unit, or false to ignore it.
| }
|
| module CUnitStatFactory
| {
| module CUnitStatFactoryHeader
| module CUnitStatFactoryFooter
| }
|
---------------------------------------------------------------------------
*/
globals
private integer requestFlag = 0
private trigger onEnterHandler = CreateTrigger()
private trigger onLeaveHandler = CreateTrigger()
// Stat Type
constant integer CSTAT_TYPE_FLOAT = 1
constant integer CSTAT_TYPE_INT = 2
// Modifier types
constant integer STAT_ADD = 1
constant integer STAT_MULT = 2
// Request types
public constant integer REQUEST_DIRECT_AMOUNT = 1
public constant integer REQUEST_MODIFY_ATTR = 2
public constant integer REQUEST_CHANGE_COUNTER = 4
// Event types
private constant integer EVENT_STAT_MODIFY = 1
private constant integer EVENT_STAT_ACTIVATE = 2
private constant integer EVENT_STAT_DEACTIVATE = 4
endglobals
static if not LIBRARY_Init then
private module Init
private static method onInit takes nothing returns nothing
call thistype.init()
endmethod
endmodule
endif
// ================================================================
// Bitwise Interface functions
// ================================================================
private function BitAnd takes integer a, integer b returns integer
static if not LIBRARY_Bitwise then
return BlzBitAnd(a, b)
else
return Bitwise.AND(a, b)
endif
endfunction
private function BitOr takes integer a, integer b returns integer
static if not LIBRARY_Bitwise then
return BlzBitOr(a, b)
else
return Bitwise.OR(a, b)
endif
endfunction
// ================================================================
private struct CUnitStatHandler extends array
// This is defined through the generated module below.
static trigger array modifyResponse
static trigger array activationResponse
static trigger array deactivationResponse
// Modify event values
readonly static integer eventIndex = 0
readonly static integer array eventType
readonly static unit array curUnit
readonly static real array prevAmt
readonly static real array curAmt
readonly static integer array prevIntAmt
readonly static integer array prevIntDivAmt
readonly static integer array curIntAmt
readonly static integer array curIntDivAmt
readonly static CStat array curStat
readonly static integer array curStatType
// Activation and deactivation event values
readonly static boolean array isActivating
// Called by the operator CStat:real=, CStat:amount=
static method prepModifyHandler takes unit whichUnit, real prev, real cur, CStat stat returns nothing
set eventIndex = eventIndex + 1
set eventType[eventIndex] = EVENT_STAT_MODIFY
set curUnit[eventIndex] = whichUnit
set prevAmt[eventIndex] = prev
set curAmt[eventIndex] = cur
set curStat[eventIndex] = stat
set curStatType[eventIndex] = CSTAT_TYPE_FLOAT
endmethod
// Called by the operator CStat:int=, CStat:divisor=
static method prepModifyIntHandler takes unit whichUnit, integer prevNum, integer curNum, integer prevDiv, integer curDiv, CStat stat returns nothing
set eventIndex = eventIndex + 1
set eventType[eventIndex] = EVENT_STAT_MODIFY
set curUnit[eventIndex] = whichUnit
set prevIntAmt[eventIndex] = prevNum
set curIntAmt[eventIndex] = curNum
set prevIntDivAmt[eventIndex] = prevDiv
set curIntDivAmt[eventIndex] = curDiv
set curStat[eventIndex] = stat
set curStatType[eventIndex] = CSTAT_TYPE_INT
endmethod
// Called by the method incActiveCounter and decActiveCounter.
static method prepActivationHandler takes unit whichUnit, boolean state, CStat stat returns nothing
set eventIndex = eventIndex + 1
set isActivating[eventIndex] = state
set curUnit[eventIndex] = whichUnit
set curStat[eventIndex] = stat
if (state) then
set eventType[eventIndex] = EVENT_STAT_ACTIVATE
else
set eventType[eventIndex] = EVENT_STAT_DEACTIVATE
endif
endmethod
static method callModifyHandler takes integer modClass returns nothing
call TriggerEvaluate(modifyResponse[modClass])
endmethod
static method callActivationHandler takes integer modClass returns nothing
call TriggerEvaluate(activationResponse[modClass])
endmethod
static method callDeactivationHandler takes integer modClass returns nothing
call TriggerEvaluate(deactivationResponse[modClass])
endmethod
static method releaseHandler takes nothing returns nothing
set eventIndex = IMaxBJ(eventIndex - 1, 0)
endmethod
private static method onEnter takes nothing returns nothing
call TriggerEvaluate(onEnterHandler)
endmethod
private static method onLeave takes nothing returns nothing
call TriggerEvaluate(onLeaveHandler)
endmethod
private static method init takes nothing returns nothing
set onEnterHandler = CreateTrigger()
set onLeaveHandler = CreateTrigger()
call OnUnitIndex(function thistype.onEnter)
call OnUnitDeindex(function thistype.onLeave)
endmethod
implement Init
endstruct
// ================================================================
private function IsFlagRequested takes integer flag returns boolean
return BitAnd(requestFlag, flag) != 0
endfunction
// ================================================================
// While technically public functions, these should not be used
// manually at any point, since the system requires that these
// functions be accessible from the module side, which cannot be
// possible when such functions are declared private.
// ================================================================
public function AssignModifuncToClass takes integer classID, code callback returns nothing
set CUnitStatHandler.modifyResponse[classID] = CreateTrigger()
call TriggerAddCondition(CUnitStatHandler.modifyResponse[classID], Condition(callback))
endfunction
public function AssignActifuncToClass takes integer classID, code callback returns nothing
set CUnitStatHandler.activationResponse[classID] = CreateTrigger()
call TriggerAddCondition(CUnitStatHandler.activationResponse[classID], Condition(callback))
endfunction
public function AssignDeactifuncToClass takes integer classID, code callback returns nothing
set CUnitStatHandler.deactivationResponse[classID] = CreateTrigger()
call TriggerAddCondition(CUnitStatHandler.deactivationResponse[classID], Condition(callback))
endfunction
// ================================================================
// These public functions are
// ================================================================
public function GetCurrentUnit takes nothing returns unit
return CUnitStatHandler.curUnit[CUnitStatHandler.eventIndex]
endfunction
public function GetPrevAmount takes nothing returns real
return CUnitStatHandler.prevAmt[CUnitStatHandler.eventIndex]
endfunction
public function GetCurrentAmount takes nothing returns real
return CUnitStatHandler.curAmt[CUnitStatHandler.eventIndex]
endfunction
public function GetPrevIntAmount takes nothing returns integer
return CUnitStatHandler.prevIntAmt[CUnitStatHandler.eventIndex]
endfunction
public function GetPrevDivisorAmount takes nothing returns integer
return CUnitStatHandler.prevIntDivAmt[CUnitStatHandler.eventIndex]
endfunction
public function GetCurrentIntAmount takes nothing returns integer
return CUnitStatHandler.curIntAmt[CUnitStatHandler.eventIndex]
endfunction
public function GetCurrentDivisorAmount takes nothing returns integer
return CUnitStatHandler.curIntDivAmt[CUnitStatHandler.eventIndex]
endfunction
public function GetCurrentStatType takes nothing returns integer
return CUnitStatHandler.curStatType[CUnitStatHandler.eventIndex]
endfunction
public function GetCurrentStat takes nothing returns CStat
return CUnitStatHandler.curStat[CUnitStatHandler.eventIndex]
endfunction
public function GetStatActivationState takes nothing returns boolean
return CUnitStatHandler.isActivating[CUnitStatHandler.eventIndex]
endfunction
public function SetRequestFlag takes integer flag returns nothing
set requestFlag = BitOr(requestFlag, flag)
endfunction
public function ResetRequestFlag takes integer flag returns nothing
set requestFlag = BitAnd(requestFlag, -flag - 1)
endfunction
public function RegisterEnterHandler takes code func returns nothing
call TriggerAddCondition(onEnterHandler, Condition(func))
endfunction
public function RegisterLeaveHandler takes code func returns nothing
call TriggerAddCondition(onLeaveHandler, Condition(func))
endfunction
// ================================================================
// ================================================================
//! runtextmacro DEFINE_LIST("", "StatList", "integer")
struct CStat extends array
implement optional GlobalAlloc
implement optional AllocT
private static TableArray hashMap = 0
// =========================================== //
// Instance Values //
// =========================================== //
method operator real takes nothing returns real
if (hashMap[3].integer[this] > 0) then
return 0.0
endif
return hashMap[0].real[this]
endmethod
method operator int takes nothing returns integer
if (hashMap[3].integer[this] > 0) then
return 0
endif
return hashMap[7].integer[this]
endmethod
method operator divisor takes nothing returns integer
if (hashMap[3].integer[this] > 0) then
return 1
endif
return hashMap[8].integer[this]
endmethod
method operator amount takes nothing returns real
return this.real
endmethod
method operator modType takes nothing returns integer
return hashMap[1].integer[this]
endmethod
private method operator activeCounter takes nothing returns integer
return hashMap[2].integer[this]
endmethod
private method operator zeroCounter takes nothing returns integer
return hashMap[3].integer[this]
endmethod
method operator owner takes nothing returns unit
return hashMap[4].unit[this]
endmethod
method operator listPtr takes nothing returns StatListItem
return hashMap[5].integer[this]
endmethod
method operator modClass takes nothing returns integer
return hashMap[6].integer[this]
endmethod
method operator statType takes nothing returns integer
return hashMap[9].integer[this]
endmethod
// =========================================== //
// Instance Values //
// =========================================== //
//! textmacro CSTAT_SETTER_HEADER takes OPNAME, TYPE, HASHINDEX
method operator $OPNAME$= takes $TYPE$ value returns nothing
if (value == this.$OPNAME$) then
return
endif
// If REQUEST_DIRECT_AMOUNT is flagged, that means
// the handlers that observe any changes will not
// be notified.
// Otherwise, if the modifier is suppressed via
// activate(false), any attempts at changing
// the value will not reflect on the affected unit,
// but will still be considered upon reactivation.
if ((IsFlagRequested(REQUEST_DIRECT_AMOUNT)) or /*
*/ (hashMap[2].integer[this] <= 0)) then
set hashMap[$HASHINDEX$].real[this] = value
return
endif
//! endtextmacro
//! textmacro CSTAT_SETTER_FOOTER takes OPNAME, TYPE, HASHINDEX
set hashMap[$HASHINDEX$].$TYPE$[this] = value
call CUnitStatHandler.callModifyHandler(hashMap[6].integer[this])
call CUnitStatHandler.releaseHandler()
endmethod
//! endtextmacro
//! runtextmacro CSTAT_SETTER_HEADER("real", "real", "0")
call CUnitStatHandler.prepModifyHandler(hashMap[4].unit[this], hashMap[0].real[this], value, this)
//! runtextmacro CSTAT_SETTER_FOOTER("real", "real", "0")
//! runtextmacro CSTAT_SETTER_HEADER("int", "integer", "7")
call CUnitStatHandler.prepModifyIntHandler(hashMap[4].unit[this], hashMap[7].integer[this], value, this.divisor, this.divisor, this)
//! runtextmacro CSTAT_SETTER_FOOTER("int", "integer", "7")
//! runtextmacro CSTAT_SETTER_HEADER("divisor", "integer", "8")
// Don't allow division by zero at all costs.
if (value == 0) then
set value = 1
endif
call CUnitStatHandler.prepModifyIntHandler(hashMap[4].unit[this], this.int, this.int, hashMap[8].integer[this], value, this)
//! runtextmacro CSTAT_SETTER_FOOTER("divisor", "integer", "8")
method operator amount= takes real newValue returns nothing
set this.real = newValue
endmethod
method operator modType= takes integer value returns nothing
if (not IsFlagRequested(REQUEST_MODIFY_ATTR)) then
return
endif
set hashMap[1].integer[this] = value
endmethod
method operator activeCounter= takes integer value returns nothing
if (not IsFlagRequested(REQUEST_MODIFY_ATTR)) then
return
endif
set hashMap[2].integer[this] = value
endmethod
method operator zeroCounter= takes integer value returns nothing
if (not IsFlagRequested(REQUEST_CHANGE_COUNTER)) then
return
endif
set hashMap[3].integer[this] = value
endmethod
method operator owner= takes unit value returns nothing
if (not IsFlagRequested(REQUEST_MODIFY_ATTR)) then
return
endif
set hashMap[4].unit[this] = value
endmethod
method operator listPtr= takes StatListItem value returns nothing
if (not IsFlagRequested(REQUEST_MODIFY_ATTR)) then
return
endif
set hashMap[5].integer[this] = value
endmethod
method operator modClass= takes integer whichClass returns nothing
if (not IsFlagRequested(REQUEST_MODIFY_ATTR)) then
return
endif
set hashMap[6].integer[this] = whichClass
endmethod
method operator statType= takes integer statType returns nothing
if (not IsFlagRequested(REQUEST_MODIFY_ATTR)) then
return
endif
set hashMap[9].integer[this] = statType
endmethod
private method incActiveCounter takes nothing returns nothing
local integer value = hashMap[2].integer[this] + 1
set hashMap[2].integer[this] = value
if (value == 1) then
call CUnitStatHandler.prepActivationHandler(hashMap[4].unit[this], true, this)
call CUnitStatHandler.callActivationHandler(hashMap[6].integer[this])
call CUnitStatHandler.releaseHandler()
endif
endmethod
private method decActiveCounter takes nothing returns nothing
local integer value = hashMap[2].integer[this] - 1
set hashMap[2].integer[this] = value
// Call activation and deactivation handlers.
if (value == 0) then
call CUnitStatHandler.prepActivationHandler(hashMap[4].unit[this], false, this)
call CUnitStatHandler.callDeactivationHandler(hashMap[6].integer[this])
call CUnitStatHandler.releaseHandler()
endif
endmethod
method nullifiesProduct takes nothing returns boolean
return this.zeroCounter > 0
endmethod
method isActive takes nothing returns boolean
return this.activeCounter > 0
endmethod
method activate takes boolean flag returns nothing
if (flag) then
call this.incActiveCounter()
else
call this.decActiveCounter()
endif
endmethod
method nullify takes boolean flag returns nothing
if (not IsFlagRequested(REQUEST_CHANGE_COUNTER)) then
return
endif
if (flag) then
set hashMap[3].integer[this] = hashMap[3].integer[this] + 1
else
set hashMap[3].integer[this] = hashMap[3].integer[this] - 1
endif
endmethod
method destroy takes nothing returns nothing
if (this.modType == 0) then
return
endif
call SetRequestFlag(REQUEST_MODIFY_ATTR)
/*
if (this.modType == STAT_ADD) then
set this.amount = 0.0
else
set this.amount = 1.0
endif
*/
// if (this.activeCounter > 0)
if (hashMap[2].integer[this] > 0) then
set hashMap[2].integer[this] = 1
call this.decActiveCounter()
endif
// Addition: (For integer-based stats)
call hashMap[9].integer.remove(this)
call hashMap[8].integer.remove(this)
call hashMap[7].integer.remove(this)
// Clear out all mapped data.
call hashMap[6].integer.remove(this)
call hashMap[5].integer.remove(this)
call hashMap[4].unit.remove(this)
call hashMap[3].integer.remove(this)
call hashMap[2].integer.remove(this)
call hashMap[1].integer.remove(this)
call hashMap[0].real.remove(this)
call this.deallocate()
call ResetRequestFlag(REQUEST_MODIFY_ATTR)
endmethod
static method create takes unit whichUnit, integer manifest returns thistype
local thistype this = thistype.allocate()
call SetRequestFlag(REQUEST_DIRECT_AMOUNT + REQUEST_MODIFY_ATTR)
if (manifest == STAT_ADD) then
set this.real = 0.0
set this.int = 0
// else if (manifest == STAT_MULT)
else
set this.real = 1.0
set this.int = 1
endif
set this.divisor = 1
set this.modType = manifest
set this.activeCounter = 1
set this.zeroCounter = 0
set this.owner = whichUnit
set this.modClass = 0
call ResetRequestFlag(REQUEST_DIRECT_AMOUNT + REQUEST_MODIFY_ATTR)
return this
endmethod
private static method init takes nothing returns nothing
set hashMap = TableArray[10]
endmethod
implement Init
endstruct
// ================================================================
module CUnitStatFactoryHeader
boolean registered
StatList addList
StatList multList
StatList zeroList
real pSum
real pProduct
real pBaseValue
integer pIntSum
integer pIntProduct
integer pIntBaseValue
// @Stub
static if not thistype.STAT_TYPE.exists then
static constant method STAT_TYPE takes nothing returns integer
return CSTAT_TYPE_FLOAT
endmethod
endif
static method apply takes unit whichUnit, real value, integer modType returns CStat
local CStat newStat = 0
local thistype this = thistype(GetUnitId(whichUnit))
if ((not this.registered) or (this == 0)) or /*
*/ ((modType != STAT_ADD) and (modType != STAT_MULT)) then
return CStat(0)
endif
set newStat = CStat.create(whichUnit, modType)
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
set newStat.modClass = thistype.typeid
set newStat.statType = thistype.STAT_TYPE()
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
set newStat.amount = value
return newStat
endmethod
static method applyInt takes unit whichUnit, integer value, integer modType returns CStat
local CStat newStat = 0
local thistype this = thistype(GetUnitId(whichUnit))
if ((not this.registered) or (this == 0)) or /*
*/ ((modType != STAT_ADD) and (modType != STAT_MULT)) then
return CStat(0)
endif
set newStat = CStat.create(whichUnit, modType)
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
set newStat.modClass = thistype.typeid
set newStat.statType = thistype.STAT_TYPE()
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
set newStat.int = value
return newStat
endmethod
// @stub
static if (not thistype.onApply.exists) then
method onApply takes unit whichUnit, real amount returns nothing
endmethod
endif
// @stub
static if (not thistype.onApplyInt.exists) then
method onApplyInt takes unit whichUnit, integer amount returns nothing
endmethod
endif
// @stub
static if not thistype.onBonusCalc.exists then
method onBonusCalc takes unit whichUnit, real base, real sum, real product, boolean zeroMultiple returns real
if (zeroMultiple) then
return sum
endif
return base*product + sum
endmethod
endif
// @stub
static if not thistype.onBonusCalcInt.exists then
method onBonusCalcInt takes unit whichUnit, integer base, integer sum, integer product, boolean zeroMultiple returns integer
if (zeroMultiple) then
return sum
endif
return base*product + sum
endmethod
endif
method bonusCalc takes unit whichUnit returns nothing
static if thistype.onApply.exists then
if thistype.STAT_TYPE() == CSTAT_TYPE_FLOAT then
call this.onApply(whichUnit, this.onBonusCalc(whichUnit, this.pBaseValue, this.pSum, this.pProduct, not this.zeroList.empty()))
endif
endif
static if thistype.onApplyInt.exists then
if thistype.STAT_TYPE() == CSTAT_TYPE_INT then
call this.onApplyInt(whichUnit, this.onBonusCalcInt(whichUnit, this.pIntBaseValue, this.pIntSum, this.pIntProduct, not this.zeroList.empty()))
endif
endif
endmethod
static method getProduct takes unit whichUnit returns real
return thistype(GetUnitId(whichUnit)).pProduct
endmethod
static method getSum takes unit whichUnit returns real
return thistype(GetUnitId(whichUnit)).pSum
endmethod
static method getBaseValue takes unit whichUnit returns real
return thistype(GetUnitId(whichUnit)).pBaseValue
endmethod
static method setBaseValueEx takes unit whichUnit, real value, boolean silentUpdate returns nothing
local thistype this = thistype(GetUnitId(whichUnit))
set this.pBaseValue = value
if (not silentUpdate) then
call this.bonusCalc(whichUnit)
endif
endmethod
static method setBaseValue takes unit whichUnit, real value returns nothing
call thistype.setBaseValueEx(whichUnit, value, false)
endmethod
static method getTotalValue takes unit whichUnit returns real
local thistype this = thistype(GetUnitId(whichUnit))
return this.onBonusCalc(whichUnit, this.pBaseValue, this.pSum, this.pProduct, not this.zeroList.empty())
endmethod
static method getIntProduct takes unit whichUnit returns integer
return thistype(GetUnitId(whichUnit)).pIntProduct
endmethod
static method getIntSum takes unit whichUnit returns integer
return thistype(GetUnitId(whichUnit)).pIntSum
endmethod
static method getIntBaseValue takes unit whichUnit returns integer
return thistype(GetUnitId(whichUnit)).pIntBaseValue
endmethod
static method setIntBaseValueEx takes unit whichUnit, integer value, boolean silentUpdate returns nothing
local thistype this = thistype(GetUnitId(whichUnit))
set this.pIntBaseValue = value
if (not silentUpdate) then
call this.bonusCalc(whichUnit)
endif
endmethod
static method setIntBaseValue takes unit whichUnit, integer value returns nothing
call thistype.setIntBaseValueEx(whichUnit, value, false)
endmethod
static method getIntTotalValue takes unit whichUnit returns integer
local thistype this = thistype(GetUnitId(whichUnit))
return this.onBonusCalcInt(whichUnit, this.pIntBaseValue, this.pIntSum, this.pIntProduct, not this.zeroList.empty())
endmethod
endmodule
module CUnitStatFactoryFooter
static method register takes unit whichUnit returns boolean
local thistype this = thistype(GetUnitId(whichUnit))
if ((this.registered) or (this == 0)) then
return false
endif
set this.registered = true
set this.pBaseValue = 0.0
set this.pSum = 0.0
set this.pProduct = 1.0
set this.pIntBaseValue = 0
set this.pIntSum = 0
set this.pIntProduct = 1
set this.addList = StatList.create()
set this.multList = StatList.create()
set this.zeroList = StatList.create()
// Perhaps the base value is overridden here, eh?
static if thistype.onRegister.exists then
call thistype.onRegister(whichUnit)
endif
return true
endmethod
private static method onModifyInt takes unit whichUnit, integer pastNum, integer curNum, integer pastDiv, integer curDiv, CStat curStat returns nothing
local thistype this = thistype(GetUnitId(whichUnit))
if (not curStat.isActive()) then
return
endif
if (curStat.modType == STAT_ADD) then
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
if (curStat.listPtr == 0) then
set curStat.listPtr = this.addList.push(curStat).last
set this.pIntSum = this.pIntSum + (curNum / curDiv)
else
set this.pIntSum = this.pIntSum - (pastNum / pastDiv) + (curNum / curDiv)
endif
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
call this.bonusCalc(whichUnit)
return
endif
if (curNum == 0) and (pastNum != 0) then
call CStatFactory_SetRequestFlag(REQUEST_CHANGE_COUNTER)
if (not curStat.nullifiesProduct()) then
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
call this.multList.erase(curStat.listPtr)
set curStat.listPtr = this.zeroList.push(curStat).last
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
// set this.pIntProduct = this.pIntProduct / pastValue
set this.pIntProduct = this.pIntProduct * (pastDiv / pastNum)
call curStat.nullify(true)
endif
call CStatFactory_ResetRequestFlag(REQUEST_CHANGE_COUNTER)
elseif (pastNum == 0) and (curNum != 0) then
call CStatFactory_SetRequestFlag(REQUEST_CHANGE_COUNTER)
if (curStat.nullifiesProduct()) then
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
call this.zeroList.erase(curStat.listPtr)
set curStat.listPtr = this.multList.push(curStat).last
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
set this.pIntProduct = this.pIntProduct * (curNum / curDiv)
call curStat.nullify(false)
endif
call CStatFactory_ResetRequestFlag(REQUEST_CHANGE_COUNTER)
else
set this.pIntProduct = this.pIntProduct * (pastDiv / pastNum) * (curNum / curDiv)
endif
call this.bonusCalc(whichUnit)
endmethod
private static method onModify takes unit whichUnit, real pastValue, real curValue, CStat curStat returns nothing
local thistype this = thistype(GetUnitId(whichUnit))
if (not curStat.isActive()) then
return
endif
if (curStat.modType == STAT_ADD) then
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
if (curStat.listPtr == 0) then
set curStat.listPtr = this.addList.push(curStat).last
set this.pSum = this.pSum + curValue
else
set this.pSum = this.pSum - pastValue + curValue
endif
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
call this.bonusCalc(whichUnit)
return
endif
if (curValue == 0.0) and (pastValue != 0.0) then
call CStatFactory_SetRequestFlag(REQUEST_CHANGE_COUNTER)
if (not curStat.nullifiesProduct()) then
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
call this.multList.erase(curStat.listPtr)
set curStat.listPtr = this.zeroList.push(curStat).last
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
set this.pProduct = this.pProduct / pastValue
call curStat.nullify(true)
endif
call CStatFactory_ResetRequestFlag(REQUEST_CHANGE_COUNTER)
elseif (pastValue == 0.0) and (curValue != 0.0) then
call CStatFactory_SetRequestFlag(REQUEST_CHANGE_COUNTER)
if (curStat.nullifiesProduct()) then
call CStatFactory_SetRequestFlag(REQUEST_MODIFY_ATTR)
call this.zeroList.erase(curStat.listPtr)
set curStat.listPtr = this.multList.push(curStat).last
call CStatFactory_ResetRequestFlag(REQUEST_MODIFY_ATTR)
set this.pProduct = this.pProduct * curValue
call curStat.nullify(false)
endif
call CStatFactory_ResetRequestFlag(REQUEST_CHANGE_COUNTER)
else
set this.pProduct = this.pProduct / pastValue * curValue
endif
call this.bonusCalc(whichUnit)
endmethod
private static method modify takes nothing returns nothing
if CStatFactory_GetCurrentStatType() == CSTAT_TYPE_FLOAT then
call thistype.onModify(CStatFactory_GetCurrentUnit(), /*
*/ CStatFactory_GetPrevAmount(), /*
*/ CStatFactory_GetCurrentAmount(), /*
*/ CStatFactory_GetCurrentStat())
else
call thistype.onModifyInt(CStatFactory_GetCurrentUnit(), /*
*/ CStatFactory_GetPrevIntAmount(), /*
*/ CStatFactory_GetCurrentIntAmount(), /*
*/ CStatFactory_GetPrevDivisorAmount(), /*
*/ CStatFactory_GetCurrentDivisorAmount(), /*
*/ CStatFactory_GetCurrentStat())
endif
endmethod
private static method onActivate takes unit whichUnit, CStat curStat, thistype this returns nothing
call CStatFactory_SetRequestFlag(CStatFactory_REQUEST_MODIFY_ATTR)
if (curStat.statType == CSTAT_TYPE_FLOAT) then
if (curStat.modType == STAT_ADD) then
set curStat.listPtr = this.addList.push(curStat).last
set this.pSum = this.pSum + curStat.amount
else
if (curStat.nullifiesProduct()) then
set curStat.listPtr = this.zeroList.push(curStat).last
else
set curStat.listPtr = this.multList.push(curStat).last
set this.pProduct = this.pProduct * curStat.amount
endif
endif
endif
if (curStat.statType == CSTAT_TYPE_INT) then
if (curStat.modType == STAT_ADD) then
set curStat.listPtr = this.addList.push(curStat).last
set this.pIntSum = this.pIntSum + curStat.int / curStat.divisor
else
if (curStat.nullifiesProduct()) then
set curStat.listPtr = this.zeroList.push(curStat).last
else
set curStat.listPtr = this.multList.push(curStat).last
set this.pIntProduct = this.pIntProduct * curStat.int / curStat.divisor
endif
endif
endif
call this.bonusCalc(whichUnit)
set curStat.listPtr = 0
call CStatFactory_ResetRequestFlag(CStatFactory_REQUEST_MODIFY_ATTR)
endmethod
private static method onDeactivate takes unit whichUnit, CStat curStat, thistype this returns nothing
local StatListItem ptr = curStat.listPtr
call CStatFactory_SetRequestFlag(CStatFactory_REQUEST_MODIFY_ATTR)
if (curStat.statType == CSTAT_TYPE_FLOAT) then
if (curStat.modType == STAT_ADD) then
call this.addList.erase(curStat.listPtr)
set this.pSum = this.pSum - curStat.amount
else
// if curStat.zeroCounter > 0
if (curStat.nullifiesProduct()) then
call this.zeroList.erase(curStat.listPtr)
else
call this.multList.erase(curStat.listPtr)
set this.pProduct = this.pProduct / curStat.amount
endif
endif
endif
if (curStat.statType == CSTAT_TYPE_INT) then
if (curStat.modType == STAT_ADD) then
call this.addList.erase(curStat.listPtr)
set this.pIntSum = this.pIntSum - curStat.int / curStat.divisor
else
// if curStat.zeroCounter > 0
if (curStat.nullifiesProduct()) then
call this.zeroList.erase(curStat.listPtr)
else
call this.multList.erase(curStat.listPtr)
set this.pIntProduct = this.pIntProduct * curStat.divisor / curStat.int
endif
endif
endif
call this.bonusCalc(whichUnit)
set curStat.listPtr = 0
call CStatFactory_ResetRequestFlag(CStatFactory_REQUEST_MODIFY_ATTR)
endmethod
private static method activate takes nothing returns nothing
local unit whichUnit = CStatFactory_GetCurrentUnit()
local CStat curStat = CStatFactory_GetCurrentStat()
local boolean state = CStatFactory_GetStatActivationState()
local thistype this = thistype(GetUnitId(whichUnit))
if (state) then
call thistype.onActivate(whichUnit, curStat, this)
else
call thistype.onDeactivate(whichUnit, curStat, this)
endif
set whichUnit = null
endmethod
private static method onUnitEnter takes nothing returns nothing
static if thistype.onFilterRegister.exists then
if not thistype.onFilterRegister(GetIndexedUnit()) then
return
endif
endif
call thistype.register(GetIndexedUnit())
endmethod
private static method onUnitLeave takes nothing returns nothing
local unit whichUnit = GetIndexedUnit()
local thistype this = thistype(GetIndexedUnitId())
local boolean wasRegistered = this.registered
local CStat curStat
// Empty all the nodes from each list
// ====================================================
loop
exitwhen not wasRegistered
loop
exitwhen this.addList.empty()
set curStat = CStat(this.addList.first.data)
call curStat.destroy()
endloop
loop
exitwhen this.multList.empty()
set curStat = CStat(this.multList.first.data)
call curStat.destroy()
endloop
loop
exitwhen this.zeroList.empty()
set curStat = CStat(this.zeroList.first.data)
call curStat.destroy()
endloop
call this.zeroList.destroy()
call this.multList.destroy()
call this.addList.destroy()
exitwhen true
endloop
// ====================================================
set this.registered = false
set this.pBaseValue = 0.0
set this.pSum = 0.0
set this.pProduct = 0.0
set this.pIntBaseValue = 0
set this.pIntSum = 0
set this.pIntProduct = 0
set this.addList = 0
set this.multList = 0
set this.zeroList = 0
static if thistype.onUnregister.exists then
if wasRegistered then
call thistype.onUnregister(whichUnit)
endif
endif
set whichUnit = null
endmethod
private static method onInit takes nothing returns nothing
call CStatFactory_AssignModifuncToClass(thistype.typeid, function thistype.modify)
call CStatFactory_AssignActifuncToClass(thistype.typeid, function thistype.activate)
call CStatFactory_AssignDeactifuncToClass(thistype.typeid, function thistype.activate)
call CStatFactory_RegisterEnterHandler(function thistype.onUnitEnter)
call CStatFactory_RegisterLeaveHandler(function thistype.onUnitLeave)
endmethod
endmodule
module CUnitStatFactory
implement CUnitStatFactoryHeader
implement CUnitStatFactoryFooter
endmodule
endlibrary
// An older version of the library that only offers custom stats for real values (not explicitly integers).
// Also the former name of the library.
library CustomUnitStatFactory requires CStatFactory
endlibrary
That's a lot of code to digest. May I present some of the examples mentioned above:
Healing Effect
Lifesteal
Attack Damage
JASS:
library HealEffect requires CStatFactory
native UnitAlive takes unit id returns boolean
struct HealEffect extends array
implement CUnitStatFactory
static method heal takes unit whichUnit, real amount returns boolean
if not UnitAlive(whichUnit) then
return false
endif
set amount = amount*HealEffect.getProduct(whichUnit) + HealEffect.getSum(whichUnit)
call SetWidgetLife(whichUnit, GetWidgetLife(whichUnit) + amount)
return true
endmethod
static method healTarget takes unit source, unit target, real amount returns boolean
if not UnitAlive(target) then
return false
elseif (source == target) then
return HealEffect.heal(target, amount)
endif
set amount = (amount*HealEffect.getProduct(source) + HealEffect.getSum(source))*HealEffect.getProduct(target)
call SetWidgetLife(target, GetWidgetLife(target) + amount)
return true
endmethod
endstruct
endlibrary
Warning: Example code not optimized.
JASS:
library Lifesteal requires CustomUnitStatFactory, optional HealEffect
struct Lifesteal extends array
implement CUnitStatFactory
private static method onHeal takes nothing returns nothing
local real lifesteal = Lifesteal.getTotalValue(GetEventDamageSource())*GetEventDamage()
if IsUnitEnemy(GetTriggerUnit(), GetOwningPlayer(GetEventDamageSource())) and BlzGetEventDamageType() == DAMAGE_TYPE_NORMAL then
static if not LIBRARY_HealEffect then
call SetWidgetLife(GetEventDamageSource(), GetWidgetLife(GetEventDamageSource()) + lifesteal)
else
// Make it play well with the Healing Effect attribute
call HealEffect.heal(GetEventDamageSource(), lifesteal)
endif
endif
endmethod
private static method init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DAMAGED)
call TriggerAddCondition(trig, Condition(function Lifesteal.onHeal))
endmethod
implement Init
endstruct
endlibrary
Warning: Does not compile! (Missing constants)
JASS:
library AttackDamage requires CustomUnitStatFactory
struct AttackDamage extends array
static constant method STAT_TYPE takes nothing returns integer
return CSTAT_TYPE_INT
endmethod
static method onApplyInt takes unit whichUnit, integer amount returns nothing
call IncUnitAbilityLevel(whichUnit, ATTACK_STAT_ABIL)
call BlzSetUnitAbilityIntegerLevelField(BlzGetUnitAbility(whichUnit, ATTACK_STAT_ABIL), ATK_STAT_FIELD, 0, amount)
call DecUnitAbilityLevel(whichUnit, ATTACK_STAT_ABIL)
endmethod
static method onRegister takes unit whichUnit returns nothing
call UnitAddAbility(whichUnit, ATTACK_STAT_ABIL)
call UnitMakeAbilityPermanent(whichUnit, true, ATTACK_STAT_ABIL)
endmethod
implement CUnitStatFactory
endstruct
endlibrary