Deprecated - new version is here: http://www.hiveworkshop.com/forums/lab-715/wurst-stathandler-249028/
But i still want to keep this threat alive as the problems i described here are very general and still unsolved.
This package can be used to manage stats, assign them to items/buffs/etc. and apply them to units. All stats have an absolute and a percental component which are stored in a way that ensures that the order of assigning different stats is irrelevant. This also removes numerical drifts (e.g. when you apply and unapply 31% damage over and over the value will not change).
Requires: http://www.hiveworkshop.com/forums/submissions-414/wurst-rational-247585/
Basic usage:
1. Config package: To define a stat, add it to the Enum Stat and call initStatType. The optional second parameter of initStatType is a lambda function which is called when the stat is changed.
2. For all items/buffs/etc. create a StatList and add stats to it by calling .add(Stat stat, int value) or .addPerc(Stat stat, int percent). This class is basically a linkedlist of stats.
3. For all units create a UnitStatBuffer. This class is an array which holds the units current stats.
4. To apply a StatList to a UnitStatBuffer call unitStatBuffer.applyStatList(statList). This also fires the StatChangedEvents of the stats which are changed, if existent.
Code:
Config (example):
Sample Application:
This already works perfectly.
The only problem I have left is that the StatChangedEvents only have the UnitStatBuffer object passed. As the UnitStatBuffer currently has no reference to an actual unit there is no way to find out which unit belongs to the UnitStatBuffer (and the other way around).
This could easily be solved by adding a member "unit target" to the UnitStatBuffer and changing its constructor to:
What I dont like about this solution is that some people prefer to use wrapper objects for their units, in that case it would be cleaner to give the UnitStatBuffer a member of that wrapper class. So id prefer to leave it to the user what kind of reference to the parent object he wants.
My first approach to do this was to make UnitStatBuffer generic:
This way the user could use UnitStatBuffer<UnitWrapper> to reference his unit wrapper or UnitStatBuffer<unit> to directly use the system with normal units.
The problem:
The apply() method of the StatChangedEvent interface has the UnitStatBuffer as an argument, so i would have to specify the generic argument here, which i dont know because i dont know how the users Wrapper class is called.
Any ideas? Of course id like suggestions/criticism about the system in general, too.
But i still want to keep this threat alive as the problems i described here are very general and still unsolved.
This package can be used to manage stats, assign them to items/buffs/etc. and apply them to units. All stats have an absolute and a percental component which are stored in a way that ensures that the order of assigning different stats is irrelevant. This also removes numerical drifts (e.g. when you apply and unapply 31% damage over and over the value will not change).
Requires: http://www.hiveworkshop.com/forums/submissions-414/wurst-rational-247585/
Basic usage:
1. Config package: To define a stat, add it to the Enum Stat and call initStatType. The optional second parameter of initStatType is a lambda function which is called when the stat is changed.
2. For all items/buffs/etc. create a StatList and add stats to it by calling .add(Stat stat, int value) or .addPerc(Stat stat, int percent). This class is basically a linkedlist of stats.
3. For all units create a UnitStatBuffer. This class is an array which holds the units current stats.
4. To apply a StatList to a UnitStatBuffer call unitStatBuffer.applyStatList(statList). This also fires the StatChangedEvents of the stats which are changed, if existent.
Code:
Java:
package StatHandler
import public StatHandlerConfig
import LinkedList
import Rational
int numStats
int numStatsMinusOne
StatChangedEvent array statChangeEvents
public interface StatChangedEvent
function apply(UnitStatBuffer buffer)
public function initStatType(Stat s)
statChangeEvents[s castTo int] = null
numStats++
public function initStatType(Stat s, StatChangedEvent e)
statChangeEvents[s castTo int] = e
numStats++
class StatAbs
Stat stat
int val
construct(Stat stat, int val)
this.stat = stat
this.val = val
class StatRel
Stat stat
rational val
construct(Stat stat, rational val)
this.stat = stat
this.val = val
public class StatList
protected LinkedList<StatAbs> statListAbs
protected LinkedList<StatRel> statListRel
construct()
statListAbs = new LinkedList<StatAbs>()
statListRel = new LinkedList<StatRel>()
/** Add ab absolute stat. */
function add(Stat s, int val)
statListAbs.add(new StatAbs(s, val))
/** Add relative stat. The value will be considered as percental.
* Example: addPerc(s, 55) will register a relative stat of 1.55 */
function addPerc(Stat s, int val)
statListRel.add(new StatRel(s, reduce(100+val, 100)))
ondestroy
for StatAbs s in statListAbs
destroy s
for StatRel s in statListRel
destroy s
destroy statListAbs
destroy statListRel
public class UnitStatBuffer
private static boolean array tmpDirty
private static int array statsAbs
private static rational array statsRel
private static int size
private int start
protected static function initialize(int s)
size = s
construct()
start = 1 + (this castTo int - 1) * size
reset()
/** Resets the absolute/relative buffer fields to 0/1. */
function reset()
for int n = start to start+size
statsAbs[n] = 0
statsRel[n] = rational(1, 1)
/** Returns the stats value. */
function getStat(Stat s) returns int
return (statsRel[start + s castTo int] * rational(statsAbs[start + s castTo int], 1)).toInt()
private function applyStatAbs(StatAbs s)
statsAbs[start + s.stat castTo int] += s.val
private function removeStatAbs(StatAbs s)
statsAbs[start + s.stat castTo int] -= s.val
private function applyStatRel(StatRel s)
statsRel[start + s.stat castTo int] *= s.val
private function removeStatRel(StatRel s)
statsRel[start + s.stat castTo int] /= s.val
private function invokeStatChangedEvents()
for n = 0 to numStatsMinusOne
if tmpDirty[n]
statChangeEvents[n].apply(this)
function applyStatList(StatList l)
for n = 0 to numStatsMinusOne
tmpDirty[n] = false
for StatAbs s in l.statListAbs
applyStatAbs(s)
tmpDirty[s.stat castTo int] = true
for StatRel s in l.statListRel
applyStatRel(s)
tmpDirty[s.stat castTo int] = true
invokeStatChangedEvents()
function removeStatList(StatList l)
for n = 0 to numStatsMinusOne
tmpDirty[n] = false
for StatAbs s in l.statListAbs
removeStatAbs(s)
tmpDirty[s.stat castTo int] = true
for StatRel s in l.statListRel
removeStatRel(s)
tmpDirty[s.stat castTo int] = true
invokeStatChangedEvents()
init
numStats = 0
initStatTypes()
UnitStatBuffer.initialize(numStats)
numStatsMinusOne = numStats-1
Config (example):
Java:
package StatHandlerConfig
import StatHandler
public enum Stat
STR
AGI
INT
public function initStatTypes()
initStatType(Stat.STR, (UnitStatBuffer b) -> print("str: " + b.getStat(Stat.STR).toString()))
initStatType(Stat.AGI, (UnitStatBuffer b) -> print("agi: " + b.getStat(Stat.AGI).toString()))
initStatType(Stat.INT, (UnitStatBuffer b) -> print("int: " + b.getStat(Stat.INT).toString()))
Sample Application:
Java:
package Test
import StatHandler
/** Spawn some heroes/items and create their UnitStatBuffers/StatLists. */
function setup()
for n = 0 to 3
let b = new UnitStatBuffer()
CreateUnit(Player(n), 'H000', 0., 0., 0.)..setUserData(b castTo int)
for n = 1 to 5
// Amulet
let sl = new StatList()..add(Stat.INT, 10)
CreateItem('I000', 100., 0.)..setUserData(sl castTo int)
for n = 1 to 5
// Sword
let sl = new StatList()..add(Stat.STR, 10)..add(Stat.AGI, 5)
CreateItem('I001', 200., 0.)..setUserData(sl castTo int)
for n = 1 to 5
// Helmet
let sl = new StatList()..add(Stat.STR, 20)
CreateItem('I002', 300., 0.)..setUserData(sl castTo int)
for n = 1 to 5
// Necklace
let sl = new StatList()..add(Stat.STR, 5)..add(Stat.AGI, 5)..add(Stat.INT, 5)
CreateItem('I003', 400., 0.)..setUserData(sl castTo int)
for n = 1 to 5
// Axe
let sl = new StatList()..addPerc(Stat.STR, 100)
CreateItem('I004', 500., 0.)..setUserData(sl castTo int)
/** Minimalist unit indexer. */
function unitStats(unit u) returns UnitStatBuffer
return u.getUserData() castTo UnitStatBuffer
/** Minimalist item indexer. */
function itemStats(item i) returns StatList
return i.getUserData() castTo StatList
function pickupItem()
unitStats(GetTriggerUnit()).applyStatList(itemStats(GetManipulatedItem()))
function dropItem()
unitStats(GetTriggerUnit()).removeStatList(itemStats(GetManipulatedItem()))
init
setup()
CreateTrigger()
..registerAnyUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM)
..addAction(function pickupItem)
CreateTrigger()
..registerAnyUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM)
..addAction(function dropItem)
This already works perfectly.
The only problem I have left is that the StatChangedEvents only have the UnitStatBuffer object passed. As the UnitStatBuffer currently has no reference to an actual unit there is no way to find out which unit belongs to the UnitStatBuffer (and the other way around).
This could easily be solved by adding a member "unit target" to the UnitStatBuffer and changing its constructor to:
Java:
construct(unit target)
this.target = target
start = 1 + (this castTo int - 1) * size
reset()
What I dont like about this solution is that some people prefer to use wrapper objects for their units, in that case it would be cleaner to give the UnitStatBuffer a member of that wrapper class. So id prefer to leave it to the user what kind of reference to the parent object he wants.
My first approach to do this was to make UnitStatBuffer generic:
Java:
public class UnitStatBuffer<T>
// old stuff...
private T parent
construct(T parent)
this.parent = parent
// old stuff...
function getParent() returns T
return parent
The problem:
The apply() method of the StatChangedEvent interface has the UnitStatBuffer as an argument, so i would have to specify the generic argument here, which i dont know because i dont know how the users Wrapper class is called.
Java:
public interface StatChangedEvent
function apply(UnitStatBuffer<?> buffer) // error, unknown type "?"
Any ideas? Of course id like suggestions/criticism about the system in general, too.
Last edited: