Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
//Resource System
//Made by Verethragna (aka D1000)
//v1.3
//For more informations read the readme
//! zinc
library ResourceSystem requires GroupUtils, TimerUtils, GetUnitCollisionSize, SimError {
private constant integer MAX_RESOURCES = 30; //Maximal number of resources, can't exceed 511
private constant integer MINE_ABILITY = 'MINE'; // The id of the "Mine"-ability
private constant string MINE_ORDER = "channel"; // The order of the "Mine"-Ability
private constant integer RETURN_ABILITY = 'RETU'; // The id of the "Return"-ability
private constant string RETURN_ORDER = "absorb"; // The order of the "Return"-Ability
private constant boolean USE_EVENTS = false; //Set this to true, if you want to use events in your map. Setting it to false will make the system a bit faster.
private constant real STORAGE_RANGE = 150, //How close the worker has to be to the storage to return the resources
STORAGE_SEARCH_RANGE = 6144, //Search range to return the resources
SOURCE_SEARCH_RANGE = 3072, //Search range to find new sources
MAX_BUILD_RANGE = 100; //I don´t know if it is posible to change it, but if you do, change this value too.
private constant string NOT_ENOUGH_TEXT = "Not enough ", //Text displayed when trying to buy something with insufficient resources
MULTIBOARD_NAME = "Resources"; //Title of the multiboard
private constant real MULTIBOARD_NAME_WIDTH = 0.05, // Width of the multiboard column for the name of the resources
MULTIBOARD_VALUE_WIDTH = 0.015, // Width of the multiboard column for the amount of the resources
BUILDING_REFUND_FACTOR = 0.75, //Percentage of the spent resources, that you'll get back when you cancel a building
ITEM_SELL_FACTOR = 0.75; //Percentage of the spent resources, that you'll get back when you sell an item
//===================================================================\\
public constant integer FARM_TYPE_INSTANT = 0,
FARM_TYPE_WALK = 1,
BUY_TYPE_SOLD = 1,
BUY_TYPE_BUILDING = 2,
BUY_TYPE_TRAINED = 3,
BUY_TYPE_ABILITY = 4;
public type ResourceEventCast extends function();
public type ResourceEventItemSale extends function();
public type ResourceEventUnitSale extends function();
public type ResourceEventTrain extends function();
public type ResourceEventBuildOrder extends function();
public type ResourceEventMine extends function(Resource, unit, widget);
public type ResourceEventReturn extends function(Resource, unit, integer, unit);
private trigger trigOnDeath;
public struct Resource{
private static thistype number, tempResource;
private static boolexpr addWorkerPick, addStoragePick, searchSourcePickUnit, searchSourcePickItem, searchSourcePickDestructable, searchStoragePick, allBuildings;
private static rect wholeMap;
private static integer tempType;
private static real tempDistance, tempX, tempY;
private static trigger inRange, trainCancelled, constructionCancelled, orderGiven;
private static multiboard properties[12];
private static widget tempWidget;
private static unit tempUnit;
private string name,
icon,
colour = "";
private integer amount[0x10];
private boolean onKill;
private sound hitSound;
private static constant integer AMOUNT_CARRIED = 0,
GAIN_PER_HIT = 0x10,
DAMAGE = 0x20,
FARM_TYPE = 0x30,
SPEED = 0x40,
ANIMATION = -1,
COSTS = -2,
BUY_TYPE = -3,
CURRENTLY_BUILDING = -4,
FINISHED = -5,
EVENT_CAST = -6,
EVENT_ITEM = -7,
EVENT_UNIT = -8,
EVENT_TRAIN = -9,
EVENT_BUILD = -10,
EVENT_MINE = -11,
EVENT_RETURN = -12,
SOURCE = 0,
MINER = 2,
MINER_STRUCT = 3,
STORAGE = 4,
ORDER_SMART = 851971;
//ORDER_RALLIED = 851970; -> GetOrderTarget does not work in some cases
private static hashtable data = InitHashtable();
static method create(string name, string icon, boolean onKill) -> thistype {
integer i;
number = thistype.allocate();
static if (DEBUG_MODE)
if(integer(number) > MAX_RESOURCES)
BJDebugMsg("|cffFF0000ResourceSystem ERROR:|r Number of resources exceeded 'MAX_RESOURCES'");
number.name = name;
number.icon = icon;
number.onKill = onKill;
return number;
}
method getName() -> string {
return name;
}
method getIcon() -> string {
return icon;
}
method addSource(integer sourceId) {
SaveInteger(data, SOURCE, sourceId, this);
}
static method getResourceBySource(integer sourceId) -> thistype {
return LoadInteger(data, SOURCE, sourceId);
}
method addStorage(integer id) {
group g;
SaveBoolean(data, STORAGE*MAX_RESOURCES+this, id, true);
if (LoadBoolean(data, STORAGE, id))
return;
SaveBoolean(data, STORAGE, id, true);
tempType = id;
GroupEnumUnitsInRect(ENUM_GROUP, wholeMap, addStoragePick);
GroupClear(ENUM_GROUP);
}
method isStorage(integer id) -> boolean {
return LoadBoolean(data, STORAGE*MAX_RESOURCES+this, id);
}
method addMiner(integer unitTypeId, string anim ,real speed, real damagePerHit, integer gain, integer amountCarried, integer farmType) {
group g;
integer i;
for(0 <= i < bj_MAX_PLAYERS) {
SaveInteger(data, (i + AMOUNT_CARRIED)*MAX_RESOURCES+this, unitTypeId, amountCarried);
SaveInteger(data, (i + GAIN_PER_HIT)*MAX_RESOURCES+this, unitTypeId, gain);
SaveReal(data, (i + DAMAGE)*MAX_RESOURCES+this, unitTypeId, damagePerHit);
SaveInteger(data, (i + FARM_TYPE)*MAX_RESOURCES+this, unitTypeId, farmType);
SaveReal(data, (i + SPEED)*MAX_RESOURCES+this, unitTypeId, speed);
SaveStr(data, ANIMATION*MAX_RESOURCES+this, unitTypeId, anim);
}
if (LoadBoolean(data, MINER, unitTypeId))
return;
SaveBoolean(data, MINER, unitTypeId, true);
tempType = unitTypeId;
GroupEnumUnitsInRect(ENUM_GROUP, wholeMap, addWorkerPick);
GroupClear(ENUM_GROUP);
}
method addProperty(integer whichPlayer, integer amount) {
this.amount[whichPlayer] += amount;
updateMultiboard(whichPlayer);
}
method setProperty(integer whichPlayer, integer amount) {
this.amount[whichPlayer] = amount;
updateMultiboard(whichPlayer);
}
method getProperty(integer whichPlayer) -> integer{
return this.amount[whichPlayer];
}
static method showMultiboard(integer whichPlayer, boolean flag) {
if (GetLocalPlayer() == Player(whichPlayer))
MultiboardDisplay(properties[whichPlayer], flag);
}
static method minimizeMultiboard(integer whichPlayer, boolean flag) {
MultiboardMinimize(properties[whichPlayer], flag);
}
//A textmacro creating the set & get functions for integer values (AmountCarried, GainPerHit & FarmType)
//! textmacro setDataInteger takes NAME, VAR
method set$NAME$(player p, integer value, integer unitTypeId) {
SaveInteger(data, (GetPlayerId(p) + $VAR$)*MAX_RESOURCES+this, unitTypeId, value);
}
method set$NAME$ById(integer p, integer value, integer unitTypeId) {
SaveInteger(data, (p + $VAR$)*MAX_RESOURCES+this, unitTypeId, value);
}
method get$NAME$(player p, integer unitTypeId) -> integer {
return LoadInteger(data, (GetPlayerId(p) + $VAR$)*MAX_RESOURCES+this, unitTypeId);
}
method get$NAME$ById(integer p, integer unitTypeId) -> integer {
return LoadInteger(data, (p + $VAR$)*MAX_RESOURCES+this, unitTypeId);
}
//! endtextmacro
//A textmacro creating the set & get functions for real values (Damage & Speed)
//! textmacro setDataReal takes NAME, VAR
method set$NAME$(player p, real value, integer unitTypeId) {
SaveReal(data, (GetPlayerId(p) + $VAR$)*MAX_RESOURCES+this, unitTypeId, value);
}
method set$NAME$ById(integer p, real value, integer unitTypeId) {
SaveReal(data, (p + $VAR$)*MAX_RESOURCES+this, unitTypeId, value);
}
method get$NAME$(player p, integer unitTypeId) -> real {
return LoadReal(data, (GetPlayerId(p) + $VAR$)*MAX_RESOURCES+this, unitTypeId);
}
method get$NAME$ById(integer p, integer unitTypeId) -> real {
return LoadReal(data, (p + $VAR$)*MAX_RESOURCES+this, unitTypeId);
}
//! endtextmacro
//! runtextmacro setDataInteger("AmountCarried", "AMOUNT_CARRIED")
//! runtextmacro setDataInteger("GainPerHit", "GAIN_PER_HIT")
//! runtextmacro setDataReal("Damage", "DAMAGE")
//! runtextmacro setDataReal("Speed", "SPEED")
//! runtextmacro setDataInteger("FarmType", "FARM_TYPE")
method setCosts(integer typeId, integer costs, integer buyType) {
SaveInteger(data, COSTS*MAX_RESOURCES+this, typeId, costs);
SaveInteger(data, BUY_TYPE, typeId, buyType);
}
method getCosts(integer typeId) -> integer {
return LoadInteger(data,COSTS*MAX_RESOURCES+this,typeId);
}
method setAnimation(integer unitTypeId, string anim) {
SaveStr(data, ANIMATION*MAX_RESOURCES+this, unitTypeId, anim);
}
method getAnimation(integer unitTypeId) -> string {
return LoadStr(data, ANIMATION*MAX_RESOURCES+this, unitTypeId);
}
method setColour(string colour) {
this.colour= "|cff" + colour;
}
method getColour() -> string {
return colour;
}
static method isBuildingFinished(unit building) -> boolean {
return LoadBoolean(data, FINISHED, GetHandleId(building));
}
method floatingValue(integer value, real x, real y) {
texttag t = CreateTextTag();
SetTextTagPos(t, x, y, 20);
SetTextTagPermanent(t, false);
SetTextTagText(t, colour + "+" + I2S(value)+ "|r", 0.023);
SetTextTagVelocity(t, 0, 0.035);
SetTextTagLifespan(t, 1.5);
SetTextTagFadepoint(t, 0.8);
t = null;
}
//A textmacro creating the functions to set static events
//! textmacro setEvent takes NAME, VAR
static method set$NAME$Event(integer id, ResourceEvent$NAME$ callback){
SaveInteger(data, EVENT_$VAR$, id, callback);
}
//! endtextmacro
//! runtextmacro setEvent("Cast", "CAST")
//! runtextmacro setEvent("ItemSale", "ITEM")
//! runtextmacro setEvent("UnitSale", "UNIT")
//! runtextmacro setEvent("Train", "TRAIN")
//! runtextmacro setEvent("BuildOrder", "BUILD")
//A textmacro creating the functions to set non-static events
//! textmacro setEvent2 takes NAME, VAR
method set$NAME$Event(ResourceEvent$NAME$ callback){
SaveInteger(data, EVENT_$VAR$, this, callback);
}
//! endtextmacro
//! runtextmacro setEvent2("Mine", "MINE")
//! runtextmacro setEvent2("Return", "RETURN")
//---------------------------------------------------------------------------------------------------------------------------------------------\\
//----------------------------------------------------Everything is private below this line----------------------------------------------------\\
//---------------------------------------------------------------------------------------------------------------------------------------------\\
private static method getDistanceBetweenPointsXYSquare(real x1, real y1, real x2, real y2) -> real {
return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
}
private method searchSource(miner worker) {
tempWidget = null;
tempDistance = SOURCE_SEARCH_RANGE*SOURCE_SEARCH_RANGE;
tempResource = this;
tempX = GetUnitX(worker.worker); tempY = GetUnitY(worker.worker);
GroupEnumUnitsInRect(ENUM_GROUP, wholeMap, searchSourcePickUnit);
GroupClear(ENUM_GROUP);
EnumItemsInRect(wholeMap, searchSourcePickItem, null);
EnumDestructablesInRect(wholeMap, searchSourcePickDestructable, null);
IssueTargetOrder(worker.worker, MINE_ORDER, tempWidget);
}
//A textmacro creating the source-searching functions for destructables, items and units
//! textmacro sourceFunc takes NAME, TYPE
private static method searchSourceFunc$NAME$() -> boolean {
$TYPE$ w = GetFilter$NAME$();
real r = getDistanceBetweenPointsXYSquare(tempX, tempY, GetWidgetX(w), GetWidgetY(w));
if(LoadInteger(data, SOURCE, Get$NAME$TypeId(w)) == tempResource && tempDistance >= r && GetWidgetLife(w) > 0.405) {
tempWidget = w;
tempDistance = r;
}
w = null;
return false;
}
//! endtextmacro
//! runtextmacro sourceFunc("Destructable", "destructable")
//! runtextmacro sourceFunc("Item", "item")
//! runtextmacro sourceFunc("Unit", "unit")
private method searchStorage(miner worker) {
integer farmType = LoadInteger(data, (worker.owner + FARM_TYPE)*MAX_RESOURCES+this, worker.unitType);
group g;
if (farmType == FARM_TYPE_INSTANT) {
addProperty(worker.owner, worker.currentlyCarryingAmount);
floatingValue(worker.currentlyCarryingAmount, GetUnitX(worker.worker), GetUnitY(worker.worker));
worker.currentlyCarryingAmount = 0;
searchSource(worker);
} else if(farmType == FARM_TYPE_WALK) {
tempX = GetUnitX(worker.worker); tempY = GetUnitX(worker.worker);
tempDistance = STORAGE_SEARCH_RANGE * STORAGE_SEARCH_RANGE;
tempResource = this;
tempUnit = null;
GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(worker.owner), searchStoragePick);
GroupClear(ENUM_GROUP);
worker.currentStorage = tempUnit;
IssueTargetOrder(worker.worker, "smart", worker.currentStorage);
}
}
private static method searchStorageFunc() -> boolean {
unit u = GetFilterUnit();
real r = getDistanceBetweenPointsXYSquare(tempX, tempY, GetWidgetX(u), GetWidgetY(u));
if (tempResource.isStorage(GetUnitTypeId(u)) && !IsUnitType(u,UNIT_TYPE_DEAD) && isBuildingFinished(u) && tempDistance > r) {
tempUnit = u;
tempDistance = r;
}
u = null;
return false;
}
//A textmacro to fill an integer array with the costs of a widget, used in multiple methods
//! textmacro setCosts
for (0 < i < number+1) {
costs[i] = Resource(i).getCosts(boughtType);
if (costs[i] > Resource(i).getProperty(GetPlayerId(buyer))) {
SimError(buyer, NOT_ENOUGH_TEXT+Resource(i).name);
enough = false;
break;
}
}
//! endtextmacro
//A textmacro to get the pay for a widget from a player, used in multiple methods
//! textmacro demandCosts
for (0 < i < number+1) {
Resource(i).addProperty(GetPlayerId(buyer), -costs[i]);
}
//! endtextmacro
//A textmacro to refund the costs of a widget to a player, used in multiple methods
//! textmacro refundCosts takes FUNC, ID, FACTOR
for (0 < i < number+1) {
Resource(i).addProperty($FUNC$(buyer), R2I(Resource(i).getCosts($ID$)$FACTOR$));
}
//! endtextmacro
private static method stopConstruction() {
timer t = GetExpiredTimer();
tempUnit u = GetTimerData(t);
DisableTrigger(constructionCancelled);
DisableTrigger(orderGiven);
IssueImmediateOrder(u.u, "stop");
EnableTrigger(orderGiven);
EnableTrigger(constructionCancelled);
ReleaseTimer(t);
u.destroy();
}
private static method stopTraining() {
timer t = GetExpiredTimer();
tempUnit u = GetTimerData(t);
DisableTrigger(trainCancelled);
IssueImmediateOrderById(u.u,851976);
EnableTrigger(trainCancelled);
ReleaseTimer(t);
u.destroy();
}
private static method onConstructionCancelled() -> boolean {
integer i, boughtType = GetUnitTypeId(GetCancelledStructure()), buyer = GetPlayerId(GetOwningPlayer(GetFilterUnit()));
for (0 < i < number+1) {
Resource(i).addProperty(buyer, R2I(Resource(i).getCosts(boughtType) * BUILDING_REFUND_FACTOR));
}
return false;
}
private static method onConstructionStart() -> boolean {
unit u, b = GetTriggerUnit();
real x = GetUnitX(b), y = GetUnitY(b);
integer id = GetUnitTypeId(b);
orderTarget o;
b = null;
GroupEnumUnitsInRange(ENUM_GROUP, x, y, MAX_BUILD_RANGE, null);
u = FirstOfGroup(ENUM_GROUP);
while (u != null) {
o = LoadInteger(data, CURRENTLY_BUILDING, GetHandleId(u));
if (o.x == x && o.y == y && o.order == id) {
o.destroy();
SaveInteger(data, CURRENTLY_BUILDING, GetHandleId(u), 0);
u = null;
break;
}
GroupRemoveUnit(ENUM_GROUP,u);
u = FirstOfGroup(ENUM_GROUP);
}
return false;
}
private static method onConstructionFinished() -> boolean {
SaveBoolean(data, FINISHED, GetHandleId(GetTriggerUnit()), true);
return false;
}
private static method onTrainCancelled() -> boolean {
integer i, boughtType = GetTrainedUnitType(), buyer = GetPlayerId(GetOwningPlayer(GetFilterUnit()));
if (boughtType == 0)
boughtType = GetResearched();
//! runtextmacro refundCosts("", "boughtType", "")
return false;
}
private static method onUnitCast() -> boolean {
integer boughtType = GetSpellAbilityId(),
buyType = LoadInteger(data, BUY_TYPE, boughtType),
i,
costs[];
boolean enough = true;
player buyer;
if (buyType == BUY_TYPE_ABILITY) {
buyer = GetTriggerPlayer();
//! runtextmacro setCosts()
if (enough)
//! runtextmacro demandCosts()
else
IssueImmediateOrder(GetTriggerUnit(), "stop");
}
static if(USE_EVENTS)
if(enough){
i = LoadInteger(data, EVENT_CAST, boughtType);
if(i != 0)
ResourceEventCast(i).evaluate();
}
return false;
}
private static method onUnitBought() -> boolean {
integer boughtType = GetUnitTypeId(GetSoldUnit()),
buyType = LoadInteger(data, BUY_TYPE, boughtType),
i,
costs[];
boolean enough = true;
player buyer;
if (buyType == BUY_TYPE_SOLD) {
buyer = GetOwningPlayer(GetBuyingUnit());
//! runtextmacro setCosts()
if (enough){
//! runtextmacro demandCosts()
static if(USE_EVENTS){
i = LoadInteger(data, EVENT_UNIT, boughtType);
if(i != 0)
ResourceEventUnitSale(i).evaluate();
}
} else {
RemoveUnit(GetSoldUnit());
//?????????????????????????????????????
}
}
return false;
}
private static method onItemBought() -> boolean {
integer boughtType = GetItemTypeId(GetSoldItem()),
buyType = LoadInteger(data, BUY_TYPE, boughtType),
i,
costs[];
boolean enough = true;
player buyer;
if (buyType == BUY_TYPE_SOLD) {
buyer = GetOwningPlayer(GetBuyingUnit());
//! runtextmacro setCosts()
if (enough){
//! runtextmacro demandCosts()
static if(USE_EVENTS){
i = LoadInteger(data, EVENT_ITEM, boughtType);
if(i != 0)
ResourceEventItemSale(i).evaluate();
}
} else {
RemoveItem(GetSoldItem());
//?????????????????????????????????????
}
}
return false;
}
private static method onItemSold() -> boolean {
integer boughtType = GetItemTypeId(GetSoldItem()),
buyType = LoadInteger(data, BUY_TYPE, boughtType),
i,
buyer = GetPlayerId(GetTriggerPlayer());
if (buyType == BUY_TYPE_SOLD) {
//! runtextmacro refundCosts("", "boughtType", "*ITEM_SELL_FACTOR")
}
return false;
}
private static method onOrder() -> boolean {
miner worker;
thistype this;
unit ordered = GetTriggerUnit();
integer targetId,
boughtType = GetIssuedOrderId(),
buyType = LoadInteger(data, BUY_TYPE, boughtType),
i,
costs[];
player buyer = GetOwningPlayer(ordered);
boolean enough = true;
timer t;
orderTarget o = LoadInteger(data, CURRENTLY_BUILDING, GetHandleId(ordered));
if (o > 0) {
//! runtextmacro refundCosts("GetPlayerId","o.order", "")
o.destroy();
}
if (buyType == BUY_TYPE_BUILDING) {
//! runtextmacro setCosts()
if (enough) {
//! runtextmacro demandCosts()
o = orderTarget.create();
o.x = GetOrderPointX();
o.y = GetOrderPointY();
o.order = boughtType;
SaveInteger(data, CURRENTLY_BUILDING, GetHandleId(ordered), o);
static if(USE_EVENTS){
i = LoadInteger(data, EVENT_BUILD, boughtType);
if(i != 0)
ResourceEventCast(i).evaluate();
}
} else {
t = NewTimer();
SetTimerData(t, tempUnit.create(ordered));
TimerStart(t, 0, false, function Resource.stopConstruction);
SaveInteger(data, CURRENTLY_BUILDING, GetHandleId(ordered), 0);
}
} else {
SaveInteger(data, CURRENTLY_BUILDING, GetHandleId(ordered), 0);
if (buyType == BUY_TYPE_TRAINED) {
//! runtextmacro setCosts()
if (enough){
//! runtextmacro demandCosts()
static if(USE_EVENTS){
i = LoadInteger(data, EVENT_TRAIN, boughtType);
if(i != 0)
ResourceEventCast(i).evaluate();
}
} else {
t = NewTimer();
SetTimerData(t, tempUnit.create(ordered));
TimerStart(t, 0, false, function Resource.stopTraining);
}
}
}
buyer = null;
if(LoadBoolean(data, MINER, GetUnitTypeId(ordered))) {
worker = LoadInteger(data, MINER_STRUCT, GetHandleId(ordered));
ordered = null;
if (worker.isMining) {
worker.isMining = false;
PauseTimer(worker.tim);
}
if (boughtType != ORDER_SMART) {
worker.currentStorage = null;
return false;
}
targetId = GetUnitTypeId(GetOrderTargetUnit());
if (targetId == 0) {
targetId = GetDestructableTypeId(GetOrderTargetDestructable());
if (targetId == 0) {
targetId = GetItemTypeId(GetOrderTargetItem());
if (targetId == 0) {
worker.currentStorage = null;
return false;
}
}
}
this = LoadInteger(data, SOURCE, targetId);
if(this > 0) {
if(LoadInteger(data, (worker.owner + AMOUNT_CARRIED)*MAX_RESOURCES+this, worker.unitType) > 0)
IssueTargetOrder(worker.worker, MINE_ORDER, GetOrderTarget());
worker.currentStorage = null;
return false;
}
this = worker.currentlyCarrying;
if (LoadBoolean(data, STORAGE*MAX_RESOURCES+this, targetId) || true)
worker.currentStorage = GetOrderTargetUnit();
} else
ordered = null;
return false;
}
private static method mine() {
miner worker = GetTimerData(GetExpiredTimer());
thistype this = worker.currentlyCarrying;
integer gain = LoadInteger(data, (worker.owner + GAIN_PER_HIT)*MAX_RESOURCES+this, worker.unitType),
max = LoadInteger(data, (worker.owner + AMOUNT_CARRIED)*MAX_RESOURCES+this, worker.unitType),
farmType = LoadInteger(data, (worker.owner + FARM_TYPE)*MAX_RESOURCES+this, worker.unitType);
real damage = LoadReal(data, (worker.owner + DAMAGE)*MAX_RESOURCES+this, worker.unitType),
life = GetWidgetLife(worker.currentTarget);
if (life <= 0.405) {
searchSource(worker);
return;
}
if (GetUnitAbilityLevel(worker.worker, RETURN_ABILITY) <= 0 && farmType == FARM_TYPE_WALK)
UnitAddAbility(worker.worker, RETURN_ABILITY);
SetWidgetLife(worker.currentTarget, life - damage);
if(!onKill){
worker.currentlyCarryingAmount += gain;
static if(USE_EVENTS){
farmType = LoadInteger(data, EVENT_MINE, this);
if(farmType != 0)
ResourceEventMine(farmType).evaluate(this, worker.worker, worker.currentTarget);
}
}
if (GetWidgetLife(worker.currentTarget) <= 0.405) {
if(onKill){
worker.currentlyCarryingAmount += gain;
static if(USE_EVENTS){
farmType = LoadInteger(data, EVENT_MINE, this);
if(farmType != 0)
ResourceEventMine(farmType).evaluate(this, worker.worker, worker.currentTarget);
}
}
if(worker.currentlyCarryingAmount >= max) {
worker.currentlyCarryingAmount = max;
searchStorage(worker);
} else
searchSource(worker);
} else if(worker.currentlyCarryingAmount >= max) {
worker.currentlyCarryingAmount = max;
searchStorage(worker);
} else {
SetUnitAnimation(worker.worker , LoadStr(data, ANIMATION*MAX_RESOURCES+worker.currentlyCarrying, worker.unitType));
TimerStart(worker.tim, LoadReal(data, (worker.owner + SPEED)*MAX_RESOURCES+this, worker.unitType), false, function Resource.mine);
}
}
private static method onSpell() -> boolean {
miner worker = LoadInteger(data, MINER_STRUCT, GetHandleId(GetTriggerUnit()));
thistype this;
integer targetId,
amount;
if(GetSpellAbilityId() == RETURN_ABILITY) {
worker.currentlyCarrying.searchStorage(worker);
} else if (GetSpellAbilityId() == MINE_ABILITY) {
targetId = GetUnitTypeId(GetSpellTargetUnit());
worker.currentTarget = GetSpellTargetUnit();
if (targetId == 0) {
targetId = GetDestructableTypeId(GetSpellTargetDestructable());
worker.currentTarget = GetSpellTargetDestructable();
if (targetId == 0) {
targetId = GetItemTypeId(GetSpellTargetItem());
worker.currentTarget = GetSpellTargetItem();
if (targetId == 0)
worker.currentStorage = null;
worker.currentTarget = null;
return false;
}
}
this = LoadInteger(data, SOURCE, targetId);
amount = LoadInteger(data, (worker.owner + AMOUNT_CARRIED)*MAX_RESOURCES+this, worker.unitType);
if (amount <= 0)
return false;
if (worker.currentlyCarrying != this) {
worker.currentlyCarrying = this;
worker.currentlyCarryingAmount = 0;
UnitRemoveAbility(worker.worker, RETURN_ABILITY);
}
if (amount <= worker.currentlyCarryingAmount) {
worker.currentlyCarryingAmount = amount;
IssueImmediateOrder(worker.worker, RETURN_ORDER);
} else {
IssueImmediateOrder(worker.worker, "stop");
SetUnitAnimation(worker.worker , LoadStr(data, ANIMATION*MAX_RESOURCES+worker.currentlyCarrying, worker.unitType));
worker.isMining = true;
TimerStart(worker.tim, LoadReal(data, (worker.owner + SPEED)*MAX_RESOURCES+this, worker.unitType), false, function Resource.mine);
}
}
return false;
}
private static method workerInRange() -> boolean {
miner worker = LoadInteger(data, MINER_STRUCT, GetHandleId(GetTriggerUnit()));
static if(USE_EVENTS) integer i;
if (worker.currentStorage != null && worker.currentlyCarryingAmount > 0 && IsUnitInRange(worker.worker, worker.currentStorage, STORAGE_RANGE+0.1)) {
UnitRemoveAbility(worker.worker, RETURN_ABILITY);
worker.currentlyCarrying.addProperty(worker.owner, worker.currentlyCarryingAmount);
worker.currentlyCarrying.floatingValue(worker.currentlyCarryingAmount, GetUnitX(worker.worker), GetUnitY(worker.worker));
static if(USE_EVENTS){
i = LoadInteger(data, EVENT_RETURN, worker.currentlyCarrying);
if(i != 0)
ResourceEventReturn(i).evaluate(worker.currentlyCarrying, worker.worker, worker.currentlyCarryingAmount, worker.currentStorage);
}
worker.currentlyCarryingAmount = 0;
worker.currentlyCarrying.searchSource(worker);
}
return false;
}
private static method addWorkerFunc() -> boolean {
if ((tempType == GetUnitTypeId(GetFilterUnit())) && (GetUnitAbilityLevel(GetFilterUnit(), MINE_ABILITY) <= 0))
SaveInteger(data, MINER_STRUCT, GetHandleId(GetFilterUnit()), miner.create(GetFilterUnit()));
return false;
}
private static method addStorageFunc() -> boolean {
if (tempType == GetUnitTypeId(GetFilterUnit()))
TriggerRegisterUnitInRange(inRange, GetFilterUnit(), STORAGE_RANGE + GetUnitCollisionSize(GetFilterUnit()), null);
return false;
}
private static method onUnitEntersMap() -> boolean {
integer id = GetUnitTypeId(GetFilterUnit());
if(LoadBoolean(data, MINER, id))
SaveInteger(data, MINER_STRUCT, GetHandleId(GetFilterUnit()), miner.create(GetFilterUnit()));
if(LoadBoolean(data, STORAGE, id))
TriggerRegisterUnitInRange(inRange, GetFilterUnit(), STORAGE_RANGE + GetUnitCollisionSize(GetFilterUnit()), null);
return false;
}
private static method pickFinishedBuildings() -> boolean {
unit u = GetFilterUnit();
if (IsUnitType(u, UNIT_TYPE_STRUCTURE))
SaveBoolean(data, FINISHED, GetHandleId(u), true);
u = null;
return false;
}
private static method onDeath() -> boolean{
unit u = GetTriggerUnit();
integer s = LoadInteger(data, CURRENTLY_BUILDING, GetHandleId(u)),
buyer,
i;
if (s > 0) {
buyer = GetPlayerId(GetOwningPlayer(u));
//! runtextmacro refundCosts("","orderTarget(s).order", "")
orderTarget(s).destroy();
}
s = LoadInteger(data, MINER_STRUCT, GetHandleId(u));
if (s > 0)
miner(s).destroy();
u = null;
return false;
}
private static method onRemove(unit u) {
integer s = LoadInteger(data, CURRENTLY_BUILDING, GetHandleId(u)),
buyer,
i;
if (s > 0) {
buyer = GetPlayerId(GetOwningPlayer(u));
//! runtextmacro refundCosts("","orderTarget(s).order", "")
orderTarget(s).destroy();
}
s = LoadInteger(data, MINER_STRUCT, GetHandleId(u));
if (s > 0)
miner(s).destroy();
}
private static method isMiner() -> boolean {
return LoadBoolean(data, MINER, GetUnitTypeId(GetFilterUnit()));
}
private static method onInit(){
trigger t = CreateTrigger(),
t2 = CreateTrigger(),
sellItem = CreateTrigger(),
buyItem = CreateTrigger(),
sellUnit = CreateTrigger(),
cast = CreateTrigger(),
finish = CreateTrigger();
region r = CreateRegion();
integer i;
wholeMap = GetWorldBounds();
RegionAddRect(r, wholeMap);
addWorkerPick = Condition(function Resource.onUnitEntersMap);
TriggerRegisterEnterRegion(t, r, addWorkerPick);
addWorkerPick = Condition(function Resource.isMiner);
inRange = CreateTrigger();
TriggerAddCondition(inRange, addWorkerPick);
TriggerAddCondition(inRange, Condition(function Resource.workerInRange));
trainCancelled = CreateTrigger();
constructionCancelled = CreateTrigger();
orderGiven = CreateTrigger();
t = CreateTrigger();
for(0 <= i < bj_MAX_PLAYERS){
TriggerRegisterPlayerUnitEvent(orderGiven, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null);
TriggerRegisterPlayerUnitEvent(orderGiven, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null);
TriggerRegisterPlayerUnitEvent(orderGiven, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null);
TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, addWorkerPick);
TriggerRegisterPlayerUnitEvent(trainCancelled, Player(i), EVENT_PLAYER_UNIT_RESEARCH_CANCEL, null);
TriggerRegisterPlayerUnitEvent(trainCancelled, Player(i), EVENT_PLAYER_UNIT_TRAIN_CANCEL, null);
TriggerRegisterPlayerUnitEvent(constructionCancelled, Player(i),EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null);
TriggerRegisterPlayerUnitEvent(finish, Player(i),EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, null);
TriggerRegisterPlayerUnitEvent(t, Player(i),EVENT_PLAYER_UNIT_CONSTRUCT_START, null);
TriggerRegisterPlayerUnitEvent(buyItem, Player(i), EVENT_PLAYER_UNIT_SELL_ITEM, null);
TriggerRegisterPlayerUnitEvent(sellItem, Player(i),EVENT_PLAYER_UNIT_PAWN_ITEM, null);
TriggerRegisterPlayerUnitEvent(sellUnit, Player(i),EVENT_PLAYER_UNIT_SELL, null);
TriggerRegisterPlayerUnitEvent(cast, Player(i),EVENT_PLAYER_UNIT_SPELL_CAST, null);
}
TriggerAddCondition(trainCancelled, Condition(function Resource.onTrainCancelled));
TriggerAddCondition(constructionCancelled, Condition(function Resource.onConstructionCancelled));
TriggerAddCondition(orderGiven, Condition(function Resource.onOrder));
TriggerAddCondition(t2, Condition(function Resource.onSpell));
TriggerAddCondition(t ,Condition(function Resource.onConstructionStart));
TriggerAddCondition(finish, Condition(function Resource.onConstructionFinished));
TriggerAddCondition(buyItem, Condition(function Resource.onItemBought));
TriggerAddCondition(sellItem, Condition(function Resource.onItemSold));
TriggerAddCondition(sellUnit, Condition(function Resource.onUnitBought));
TriggerAddCondition(cast, Condition(function Resource.onUnitCast));
trigOnDeath = CreateTrigger();
TriggerAddCondition(trigOnDeath, Condition(function Resource.onDeath));
addWorkerPick = Condition(function Resource.addWorkerFunc);
addStoragePick = Condition(function Resource.addStorageFunc);
searchSourcePickUnit = Condition(function Resource.searchSourceFuncUnit);
searchSourcePickDestructable = Condition(function Resource.searchSourceFuncDestructable);
searchSourcePickItem = Condition(function Resource.searchSourceFuncItem);
searchStoragePick = Condition(function Resource.searchStorageFunc);
allBuildings = Condition(function Resource.pickFinishedBuildings);
TimerStart(CreateTimer(), 0, false, function Resource.createMultiboards);
t = null;
t2 = null;
sellItem = null;
buyItem = null;
sellUnit = null;
static if (DEBUG_MODE)
if(MAX_RESOURCES > 511 || MAX_RESOURCES <= 0)
BJDebugMsg("|cffFF0000ResourceSystem ERROR:|r 'MAX_RESOURCES' must be set to a positive value lower than 511");
}
private static method createMultiboards() {
integer i, j, k = number;
thistype this;
multiboarditem mi;
GroupEnumUnitsInRect(ENUM_GROUP, wholeMap, allBuildings);
ReleaseTimer(GetExpiredTimer());
for(0 <= i < 12) {
properties[i] = CreateMultiboard();
MultiboardSetTitleText(properties[i], MULTIBOARD_NAME);
MultiboardSetColumnCount(properties[i], 2);
MultiboardSetRowCount(properties[i], k);
if(GetLocalPlayer() == Player(i))
MultiboardDisplay(properties[i], true);
for(0 <= j < k) {
this = j+1;
mi = MultiboardGetItem(properties[i], j, 0);
MultiboardSetItemIcon(mi, icon);
MultiboardSetItemValue(mi, colour + name + "|r");
MultiboardSetItemWidth(mi, MULTIBOARD_NAME_WIDTH);
MultiboardReleaseItem(mi);
mi = MultiboardGetItem(properties[i], j, 1);
MultiboardSetItemStyle(mi, true, false);
MultiboardSetItemValue(mi, colour + I2S(amount[i]) + "|r");
MultiboardSetItemWidth(mi, MULTIBOARD_VALUE_WIDTH);
MultiboardReleaseItem(mi);
}
}
mi = null;
}
private static method updateMultiboard(integer whichPlayer) {
integer i, j, k = number;
thistype this;
multiboarditem mi;
MultiboardSetRowCount(properties[whichPlayer], k);
for(0 <= j < k) {
this = j+1;
mi = MultiboardGetItem(properties[whichPlayer], j, 0);
MultiboardSetItemIcon(mi, icon);
MultiboardSetItemValue(mi, colour + name + "|r");
MultiboardSetItemWidth(mi, MULTIBOARD_NAME_WIDTH);
MultiboardReleaseItem(mi);
mi = MultiboardGetItem(properties[whichPlayer], j, 1);
MultiboardSetItemStyle(mi, true, false);
MultiboardSetItemValue(mi, colour + I2S(amount[whichPlayer]) + "|r");
MultiboardSetItemWidth(mi, MULTIBOARD_VALUE_WIDTH);
MultiboardReleaseItem(mi);
}
mi = null;
}
private method destroy() {}
}
private struct miner {
unit worker,
currentStorage;
Resource currentlyCarrying;
integer currentlyCarryingAmount,
unitType,
owner;
widget currentTarget;
real currentTargetX, currentTargetY;
timer tim;
boolean isMining = false;
static method create(unit u) -> thistype{
thistype this = thistype.allocate();
worker = u;
unitType = GetUnitTypeId(u);
owner = GetPlayerId(GetOwningPlayer(u));
tim = NewTimer();
SetTimerData(tim, this);
UnitAddAbility(u, MINE_ABILITY);
if (!IsUnitType(u, UNIT_TYPE_HERO))
TriggerRegisterUnitEvent(trigOnDeath, u, EVENT_UNIT_DEATH);
return this;
}
private method onDestroy() {ReleaseTimer(tim);}
}
private struct tempUnit {
unit u;
static method create(unit u) -> tempUnit {
thistype this = thistype.allocate();
this.u = u;
return this;
}
}
private struct orderTarget {
real x, y;
integer order;
}
}
//! endzinc
hook RemoveUnit Resource.onRemove
//TESH.scrollpos=1
//TESH.alwaysfold=0
Resource System
Version 1.3
Made by Verethragna (aka D1000)
//==================================================\\
Requirements:
Libraries:
GetUnitCollisionSize made by Verethragna (aka D1000)
GroupUtils made by Rising_Dusk
TimerUtils made by Vexorian
SimError made by Vexorian
Abilities:
Mine
Return
//==================================================\\
Credits to:
Vexorian: TimerUtils, SimError, JassHelper
Rising_Dusk: GroupUtils
Cedi: Got an idea how to get the constructing unit in the event EVENT_PLAYER_UNIT_CONSTRUCT_START
//==================================================\\
Import Instructions:
1) Copy the ResourceSystem-Folder & the required libraries to your map
2) Copy the two required abilities to your map (required abilities & libraries listed above)
3) Adjust the constants in the "ResourceSystem"-Library
4) Use the methods listed in "ResourceSystem API Resource Creation" to create and adjust new resources
5) Give credits to me & the people who made the required libraries
6) Enjoy
//==================================================\\
//TESH.scrollpos=0
//TESH.alwaysfold=0
Resource System
Version 1.3
Made by Verethragna (aka D1000)
//==================================================\\
//Methods to create, adjust and manipulate resources.
//==================================================\\
____________________________________________
static method create(string name, string icon, boolean onKill) -> thistype
//Creates a new Resource.
//Usage:
local Resource yourResource = Resource.create("name","icon", true/false)
//Parameters:
name -> Name of the Resource
icon -> Multiboard-Icon of the Resource
onKill -> Defines if you get this resource each hit on a source or only if you kill it.
____________________________________________
method addSource(integer sourceId)
//Adds a source to the Resource
//Usage:
call yourResource.addSource('type')
//Parameter:
sourceId -> type-id of the source. Can be an item a unit or a destructable, but it must be selectable ingame.
____________________________________________
static method getResourceBySource(integer sourceId) -> thistype
//Returns the resource that can be extracted from the given source.
//Usage:
local Resource yourResource = getResourceBySource('type')
//Parameter:
sourceId -> type-id of the source
____________________________________________
method addStorage(integer id)
//Adds a Storage-building to the Resource. Only needed if your workers have the farm type "FARM_TYPE_WALK"
//Usage:
call yourResource.addStorage('type')
//Parameter:
id -> type-id of the storage. Should be a building.
____________________________________________
method isStorage(integer id) -> boolean
//Returns true if the given building is a storage of your resource
//Usage:
local boolean b = yourResource.isStorage('type')
//Paramter:
id -> The id of the building you want to check.
____________________________________________
method addMiner(integer unitTypeId, string anim ,real speed, real damagePerHit, integer gain, integer amountCarried, integer farmType)
//Adds a miner to the resource
//Usage:
call yourResource.addMiner('type', "animation", 0.0, 0.0, 0, 0, FARM_TYPE_INSTANT/WALK)
//Parameters:
unitTypeId -> type-id of the worker
anim -> animation that is played when the unit mines this resource
speed -> mining-speed
damagePerHit -> damage done to the source per hit
gain -> resource-gain per hit/kill
amountCarried -> how much of the resource the worker can mine until he brings it back to a storage. Should always be higher then "gain", even if the farm type is "FARM_TYPE_WALK"
farmType -> Either "FARM_TYPE_INSTANT" or "FARM_TYPE_WALK". Defines if the worker has to bring the mined resources back to a storage
____________________________________________
method setCosts(integer typeId, integer costs, integer buyType)
//Sets the costs of a Unit/Building/Item/Research
//Usage:
call yourResource.setCosts('type', 0, BUY_TYPE_SOLD/_BUILDING/_TRAINED/_ABILITY)
//Parameters:
typeId -> type-id of the object
costs -> how much the object should cost
buyType -> how the object is bought. If you add costs to this object for different resources, the buyType is set in the last call.
(For example: call yourResource1.setCosts('type', 10, 0); call yourResource2.setCosts('type', 30, BUY_TYPE_SOLD); -> The buyType is BUY_TYPE_SOLD)
____________________________________________
method getCosts(integer typeId) -> integer
//Gets the costs of an object
//Usage:
local integer i = yourResource.getCosts(typeId)
//Parameter:
typeId -> the type-id of the unit/building/item/research
____________________________________________
method getName() -> string
//Returns the name of your resource
//Usage:
local string s = yourResource.getName()
____________________________________________
method getIcon() -> string
//Returns the icon of your resource
//Usage:
local string s = yourResource.getIcon()
____________________________________________
method setAmountCarried(player p, integer value, integer typeId)
//Changes the amount of you resources that can be carried by the given unit and player
//Usage:
call yourResource.setAmountCarried(Player(0), 0, 'type')
//Paramters:
p -> The player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method setAmountCarriedById(integer p, integer value, integer typeId)
//Changes the amount of you resources that can be carried by the given unit and player
//Usage:
call yourResource.setAmountCarried(0, 0, 'type')
//Paramters:
p -> The id of the player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method getAmountCarried(player p, integer typeId)
//Gets the amount of you resources that can be carried by the given unit and player
//Usage:
local integer i = yourResource.getAmountCarried(Player(0), 'type')
//Paramters:
p -> The player
typeId -> The unitType
____________________________________________
method getAmountCarriedById(integer p, integer typeId)
//Gets the amount of you resources that can be carried by the given unit and player
//Usage:
local integer i = yourResource.getAmountCarried(0, 'type')
//Paramters:
p -> The id of the player
typeId -> The unitType
____________________________________________
method setGainPerHit(player p, integer value, integer typeId)
//Changes the gain per hit of the given unit and player
//Usage:
call yourResource.setGainPerHit(Player(0), 0, 'type')
//Paramters:
p -> The player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method setGainPerHitById(integer p, integer value, integer typeId)
//Changes the gain per hit of the given unit and player
//Usage:
call yourResource.setGainPerHit(0, 0, 'type')
//Paramters:
p -> The id of the player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method getGainPerHit(player p, integer typeId)
//Gets the gain per hit of the given unit and player
//Usage:
local integer i = yourResource.getGainPerHit(Player(0), 'type')
//Paramters:
p -> The player
typeId -> The unitType
____________________________________________
method getGainPerHitById(integer p, integer typeId)
//Gets the gain per hit of the given unit and player
//Usage:
local integer i = yourResource.getGainPerHit(0, 'type')
//Paramters:
p -> The id of the player
typeId -> The unitType
____________________________________________
method setDamage(player p, real value, integer typeId)
//Changes the damage per hit of the given unit and player
//Usage:
call yourResource.setDamage(Player(0), 0.0, 'type')
//Paramters:
p -> The player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method setDamageById(integer p, real value, integer typeId)
//Changes the damage per hit of the given unit and player
//Usage:
call yourResource.setDamage(0, 0.0, 'type')
//Paramters:
p -> The id of the player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method getDamage(player p, integer typeId) -> real
//Gets the damage per hit of the given unit and player
//Usage:
local integer i = yourResource.getDamage(Player(0), 'type')
//Paramters:
p -> The player
typeId -> The unitType
____________________________________________
method getDamageById(integer p, integer typeId) -> real
//Gets the damage per hit of the given unit and player
//Usage:
local integer i = yourResource.getDamage(0, 'type')
//Paramters:
p -> The id of the player
typeId -> The unitType
____________________________________________
method setSpeed(player p, real value, integer typeId)
//Changes the speed of the given unit and player
//Usage:
call yourResource.setSpeed(Player(0), 0.0, 'type')
//Paramters:
p -> The player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method setSpeedById(integer p, real value, integer typeId)
//Changes the speed of the given unit and player
//Usage:
call yourResource.setSpeed(0, 0.0, 'type')
//Paramters:
p -> The id of the player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method getSpeed(player p, integer typeId) -> real
//Gets the speed of the given unit and player
//Usage:
local integer i = yourResource.getSpeed(Player(0), 'type')
//Paramters:
p -> The player
typeId -> The unitType
____________________________________________
method getSpeedById(integer p, integer typeId) -> real
//Gets the speed of the given unit and player
//Usage:
local integer i = yourResource.getSpeed(0, 'type')
//Paramters:
p -> The id of the player
typeId -> The unitType
___________________________________________
method setFarmType(player p, integer value, integer typeId)
//Changes the FarmType of the given unit and player
//Usage:
call yourResource.setFarmType(Player(0), 0, 'type')
//Paramters:
p -> The player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method setFarmTypeById(integer p, integer value, integer typeId)
//Changes the FarmType of the given unit and player
//Usage:
call yourResource.setFarmType(0, 0, 'type')
//Paramters:
p -> The id of the player who is affected by the change
value -> The new value
typeId -> The affected unitType
____________________________________________
method getFarmType(player p, integer typeId)
//Gets the FarmType of the given unit and player
//Usage:
local integer i = yourResource.getFarmType(Player(0), 'type')
//Paramters:
p -> The player
typeId -> The unitType
____________________________________________
method getFarmTypeById(integer p, integer typeId)
//Gets the FarmType of the given unit and player
//Usage:
local integer i = yourResource.getFarmType(0, 'type')
//Paramters:
p -> The id of the player
typeId -> The unitType
____________________________________________
method setColour(string colour)
//Sets the colour of the Resource in the multiboard and in the text-tags
//Usage:
call yourResource.setColour("000000")
//Parameter:
colour -> The colour as a hexadezimal number in a string. For example: "FF0000" is red, "000000" is black.
____________________________________________
method getColour() -> string
//Returns the colour of the Resource in the multiboard and in the text-tags in the format |cffXXXXXX
//Usage:
local string s = yourResource.getColour()
_____________________________________________
method setAnimation(integer unitTypeId, string anim)
//Sets the animation that is played by the given unitType when it mines this resource
//Usage:
call yourResource.setAnimation('type', "animation")
//Parameters:
unitTypeId -> The id of the unitType
anim -> The animation that should be played
_____________________________________________
method getAnimation(integer unitTypeId)
//Returns the animation that is played by the given unitType when it mines this resource
//Usage:
set string s = yourResource.setAnimation('type')
//Parameters:
unitTypeId -> The id of the unitType
_____________________________________________
method addProperty(integer whichPlayer, integer amount)
//Adds the given value to the amount of your Resource a players has. Use negative values to decrease it
//Usage:
call yourResource.addProperty(0, 0)
//Parameters
whichPlayer -> The id of the player
amount -> The amount which will be added to the chosen players properties
____________________________________________
method setProperty(integer whichPlayer, integer amount)
//Sets the given value to the amount of your Resource a players has. Use negative values to decrease it
//Usage:
call yourResource.setProperty(0, 0)
//Parameters
whichPlayer -> The id of the player
amount -> The properties of the chosen player will be set to this value
____________________________________________
method getProperty(integer whichPlayer) -> integer
//Returns the amount of your Resource the given player has.
//Usage:
local integer i = yourResource.getProperty(0)
//Parameters
whichPlayer -> The id of the player
_____________________________________________
//TESH.scrollpos=0
//TESH.alwaysfold=0
Resource System
Version 1.3
Made by Verethragna (aka D1000)
//==================================================\\
//Methods used for various things, not necessarily concerning resources.
//==================================================\\
___________________________________________
static method showMultiboard(integer whichPlayer, boolean flag)
//Shows/Hides the multiboard with the Resources of the given player to the given player
//Usage:
call Resource.showMultiboard(0, true/false)
//Parameters:
whichPlayer -> The id of the player
flag -> if true -> shows the multiboard, false -> hides the multiboard
_____________________________________________
static method minimizeMultiboard(integer whichPlayer, boolean flag)
//Minimizes/Maximizes the multiboard with the Resources of the given player
//Usage:
call Resource.minimizeMultiboard(0, true/false)
//Parameters:
whichPlayer -> The id of the player
flag -> if true -> minimizes the multiboard, false -> maximizes the multiboard
____________________________________________
static method isBuildingFinished(unit building) -> boolean
//Returns if the given building is finished, also works for building which are not used by the system
//Usage:
set boolean b = Resource.isBuildingFinished(yourBuilding)
//Parameters
building -> Your building
____________________________________________
method floatingValue(integer value, real x, real y)
//Creates a floating number in the resources colour at the given point
//Usage:
call yourResource.floatingValue(0,0.0,0.0)
//Parameters
value -> shown value
x -> x-coordinate of the value
y -> y-coordinate of the value
____________________________________________
//TESH.scrollpos=0
//TESH.alwaysfold=0
Resource System
Version 1.3
Made by Verethragna (aka D1000)
//==================================================\\
//Methods for setting events. Events can be used instead of triggers (-> less triggers) and only react, when the player has enough resources
// (or there aren't any resource-costs anyway)
//Don't forget to set 'USE_EVENTS' to 'true', if you want to use events.
//You can use 0 as function name, if you want to reset the event
//==================================================\\
___________________________________________
static method setCastEvent(integer id, ResourceEventCast callback)
//Sets the function, which is to be called, when the given ability is casted
//Usage:
call Resource.setCastEvent('type', functionName)
//Parameters:
id -> type-id of the ability which should trigger the event
callback -> Function which should be called
//Type:
public type ResourceEventCast extends function();
___________________________________________
static method setItemSaleEvent(integer id, ResourceEventItemSale callback)
//Sets the function, which is to be called, when the given item is bought by a unit
//Usage:
call Resource.setItemSaleEvent('type', functionName)
//Parameters:
id -> type-id of the item which should trigger the event
callback -> Function which should be called
//Type:
public type ResourceEventItemSale extends function();
___________________________________________
static method setUnitSaleEvent(integer id, ResourceEventUnitSale callback)
//Sets the function, which is to be called, when the given unit is brought (not trained!)
//Usage:
call Resource.setUnitSaleEvent('type', functionName)
//Parameters:
id -> Id of the unit which should trigger the event
callback -> Function which should be called
//Type:
public type ResourceEventUnitSale extends function();
___________________________________________
static method setTrainEvent(integer id, ResourceEventTrain callback)
//Sets the function, which is to be called, when the given unit/upgrade is trained/researched
//Usage:
call Resource.setTrainEvent('type', functionName)
//Parameters:
id -> Id of the unit or upgrade which should trigger the event
callback -> Function which should be called
//Type:
public type ResourceEventTrain extends function();
___________________________________________
static method setBuildOrderEvent(integer id, ResourceEventBuildOrder callback)
//Sets the function, which is to be called, when a unit is ordered to construct the given building
//Usage:
call Resource.setBuildOrderEvent('type', functionName)
//Parameters:
id -> Id of the building which should trigger the event
callback -> Function which should be called
//Type:
public type ResourceEventBuildOrder extends function();
___________________________________________
method setMineEvent(ResourceEventMine callback)
//Sets the function, which is to be called, when the given resource is mined
//Usage:
call yourResource.setMineEvent(functionName)
//Parameters:
callback -> Function which should be called
//Type:
public type ResourceEventMine extends function(Resource, unit, widget);
//Type-Parameters:
Resource -> The resource this reacts to
unit -> The mining unit
widget -> The unit, item or destructable which is mined
___________________________________________
method setReturnEvent(ResourceEventReturn callback)
//Sets the function, which is to be called, when the given resource is brought to a storage
//Usage:
call yourResource.setReturnEvent(functionName)
//Parameters:
callback -> Function which should be called
//Type:
public type ResourceEventReturn extends function(Resource, unit, integer, unit);
//Type-Parameters:
Resource -> The resource this reacts to
unit -> The unit, which brings the resource
integer -> Amount of the resource, which si brought
unit -> The storage-unit
___________________________________________
//TESH.scrollpos=0
//TESH.alwaysfold=0
_____________________________________________
|'v1.3 - December 22th, 2011'|
|'Bugs fixed:'|
|---
|'New features:'|
|• Text displayed when trying to buy something with insufficient resources is now changeable
|• Added "getColour"-method
|• Added events (see "ResourceSystem API Events" for details)
|• Added comments to the examples
|• "floatingValue"-method is now public accessible (see "ResourceSystem API Misc" for details)
|'Other changes:'|
|• Fixed mistakes in the Readme
|• The system now requires only one hashtable, instead of one plus one per resource
|• Restructured Readme in multiple files
|• Changed "Known Bugs" to "Known Problems"
_____________________________________________
|'v1.2 - July 20th, 2011'|
|'Bugs fixed:'|
|---
|'New features:'|
|• "GetUnitCollisionSize" now has functions for units and unittypes
|• Commented the textmacros
|• Added import instructions to the readme
|'Other changes:'|
|• Improved "GetCollisionSize" and renamed it to "GetUnitCollisionSize"
|• Changed the respective function-calls in the resource-system
|• Now using GroupUtil´s "ENUM_GROUP" instead of creating a new group in some cases
|• Renamed the Readme, Changelog & Known Bugs, to avoid name-conflicts with other systems
_____________________________________________
|'v1.1 - June 25th, 2011'|
First release on "http://www.hiveworkshop.com" (english)
|'Bugs fixed:'|
|• Fixed an issue involving "smart"-orders targetting items
|'New features:'|
|• Added "Changelog"
|• Added "Known Bugs"
|'Other changes:'|
|• Made the methods "minimizeMultiboard" and "showMultiboard" static
|• Changed my name in the description and everywhere else to the more up-to-date "Verethragna"
|• Tried to add rally-point-support, failed
_____________________________________________
|'v1.0 - July 6th, 2010'|
First release, only on "http://warcraft.ingame.de/forum/showthread.php?t=203327" (german)
|´'Bugs fixed:'|
|• Its not possible anymore to return resources to unfinished buildings
|'New features:'|
|• Resources can now be used to buy things like items or units...
|• ...and respective methods were added
|• added static method "isBuildingFinished"
|'Other changes:'|
|• Now using TriggerAddCondition instead of TriggerAddAction
_____________________________________________
|'v0 - Somewhen between February 13th and 27th, 2010'|
Unfinished version, submitted to the "Trigger-it-Out"-Contest on "http://warcraft.ingame.de/forum/showthread.php?t=198065" (german)
|'Bugs fixed:'|
|---
|'New features:'|
|• Resource System
|• Readme
|• GetCollisionSize System
|'Other changes:'|
|---
_____________________________________________
//TESH.scrollpos=0
//TESH.alwaysfold=0
|____________________________________________________________________________________________________
|"Bugs which are known to me, but I don't know how to fix:"|
|____________________________________________________________________________________________________
|• No Rally-Point-Support
|• Nearest storage/resource is determined via beeline, thus there are problems if the way is blocked
| (Could solve this with a path-finding-algorithm, but it would probably need to much computing)
|• Trying to buy an item/merc without having enough of a custom resource reduces the item/merc stock by 1
|____________________________________________________________________________________________________
//TESH.scrollpos=0
//TESH.alwaysfold=0
globals
Resource stone //Global, because it is needed for the "Enhanced Stone Mining"-upgrade
endglobals
scope ResourceInit initializer init
function init takes nothing returns nothing
//Creates the resource "flesh"
local Resource r = Resource.create("Flesh","ReplaceableTextures\\CommandButtons\\BTNMonsterLure.blp", true)
//Sets the colour used in the muliboard & floating numbers
call r.setColour("FFA0A0")
//Gives 5 "Flesh" to player 0 (1)
call r.addProperty(0, 5)
//Allows footmen to mine "Flesh"
call r.addMiner('hfoo', "attack", 0.8, 50, 1, 1, FARM_TYPE_INSTANT)
//Makes rabbits and chicken to "Flesh"-sources
call r.addSource('necr')
call r.addSource('nech')
//Adds "Flesh"-costs to meat, fire elementals and footmen
call r.setCosts('meat', 1, BUY_TYPE_SOLD)
call r.setCosts('fire', 1, BUY_TYPE_SOLD)
call r.setCosts('hfoo', 1, BUY_TYPE_TRAINED)
//Creates the "Stone"-resource
set stone = Resource.create("Stone", "ReplaceableTextures\\CommandButtons\\BTNResistantSkin.blp", false)
//Sets the colour used in the muliboard & floating numbers
call stone.setColour("AAAAAA")
//Gives 200 "Stone" to player 0 (1)
call stone.addProperty(0, 200)
//Allows footmen to mine "Stone"
call stone.addMiner('hfoo', "attack defend", 0.8, 1, 1, 10, FARM_TYPE_WALK)
//Makes destructible rocks to "Stone"-sources
call stone.addSource('LTrc')
//Makes all kind of main-buildings to possible "Stone"-storages (i.e. footmen can bring their mined stones there)
call stone.addStorage('htow')
call stone.addStorage('hkee')
call stone.addStorage('hcas')
call stone.addStorage('ogre')
call stone.addStorage('ostr')
call stone.addStorage('ofrt')
call stone.addStorage('unpl')
call stone.addStorage('unp1')
call stone.addStorage('unp2')
call stone.addStorage('etol')
call stone.addStorage('etoa')
call stone.addStorage('etoe')
//Adds "Stone"-costs to campfires, the "Enhanced Stone Mining"-upgrade and the "Hurl Boulder"-ability
call stone.setCosts('camp', 100, BUY_TYPE_BUILDING)
call stone.setCosts('STON', 50, BUY_TYPE_TRAINED)
call stone.setCosts('ACtb', 1, BUY_TYPE_ABILITY)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Enhanced_Stone_Mining_Conditions takes nothing returns boolean
return GetResearched() == 'STON'
endfunction
function Trig_Enhanced_Stone_Mining_Actions takes nothing returns nothing
//Allows footmen owned by player 0 (1) to mine "Stone" faster
call stone.setSpeedById(0, 0.6, 'hfoo')
//Allows footmen owned by player 0 (1) to carry more "Stone"
call stone.setAmountCarriedById(0, 15, 'hfoo')
endfunction
//===========================================================================
function InitTrig_Upgrade_Example takes nothing returns nothing
set gg_trg_Upgrade_Example = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Upgrade_Example, EVENT_PLAYER_UNIT_RESEARCH_FINISH)
call TriggerAddCondition( gg_trg_Upgrade_Example, Condition( function Trig_Enhanced_Stone_Mining_Conditions ) )
call TriggerAddAction( gg_trg_Upgrade_Example, function Trig_Enhanced_Stone_Mining_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
//Set 'USE_EVENTS' in the ResourceSystem to 'true', to see this example in action
function InitTrig_Event_Example takes nothing returns nothing
//Sets the function "onBoulderCast" as event, when "Hurl Boulder" is casted
call Resource.setCastEvent('ACtb', onBoulderCast)
//Sets the function "onRockHit" as event, when "Stone" is mined
call stone.setMineEvent(onRockHit)
endfunction
//Functions which will be called , can be declared anywhere
function onBoulderCast takes nothing returns nothing
call BJDebugMsg("Hurl Boulder Event")
endfunction
function onRockHit takes Resource r, unit worker, widget source returns nothing
call BJDebugMsg("Stone Mine Event, lives left: "+I2S(R2I(GetWidgetLife(source)+0.5)))
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
//GetUnitCollisionSize
//by Verethragna (aka D1000)
//v1.1
//Use "GetUnitCollsionSize(unit)" or "GetUnitTypeCollsionSize(unittype)" to get the approximate collsion size of a unit
//Based on Vexorians "GetUnitCollsionSize", but made in ZinC & saves values in a hashtable (if enabled)
//! zinc
library GetUnitCollisionSize requires optional xebasic {
//Only needed if xebasic is not used
private constant real MAX_COLLSION_SIZE = 197;
//Number of iterations used, the more, the accurate
private constant integer ITERATIONS = 20;
//If you want that collsion-values are saved in hashtables, set this to true. Else it will be calculated every time again.
private constant boolean USE_HASHTABLE = true;
//The position of eventual temporal units, if you use "GetUnitTypeCollisionSize"
private constant real UNIT_POS_X = 0;
private constant real UNIT_POS_Y = 0;
//==================================================================================================\\
//==================================================================================================\\
private hashtable savedValues;
//A textmacro containing the algorithm for getting the collision size, since it is used in two different functions
//! textmacro unitCollisionSize
static if (LIBRARY_xebasic)
higher = XE_MAX_COLLISION_SIZE;
else
higher = MAX_COLLSION_SIZE;
lower = 0;
for(0 <= i < ITERATIONS) {
middle = (lower+higher)/2;
if (IsUnitInRangeXY(u, x+middle, y, 0))
lower = middle;
else
higher = middle;
}
static if (USE_HASHTABLE)
SaveReal(savedValues, 0, uType, middle);
//! endtextmacro
public function GetUnitCollisionSize(unit u) -> real {
real lower, higher, middle, i, x, y;
static if (USE_HASHTABLE) {
integer uType = GetUnitTypeId(u);
lower = LoadReal(savedValues, 0, uType);
if(lower > 0)
return lower;
}
x = GetUnitX(u); y = GetUnitY(u);
//! runtextmacro unitCollisionSize()
return middle;
}
public function GetUnitTypeCollisionSize(integer uType) -> real {
real lower, higher, middle, i, x = UNIT_POS_X, y = UNIT_POS_Y;
unit u;
static if (USE_HASHTABLE) {
lower = LoadReal(savedValues, 0, uType);
if(lower > 0)
return lower;
}
u = CreateUnit(Player(15), uType, 0, 0, 0);
ShowUnit(u, false);
//! runtextmacro unitCollisionSize()
RemoveUnit(u);
u = null;
return lower;
}
private function onInit() {
static if (USE_HASHTABLE)
savedValues = InitHashtable();
}
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library SimError initializer init
//**************************************************************************************************
//*
//* SimError
//*
//* Mimic an interface error message
//* call SimError(ForPlayer, msg)
//* ForPlayer : The player to show the error
//* msg : The error
//*
//* To implement this function, copy this trigger and paste it in your map.
//* Unless of course you are actually reading the library from wc3c's scripts section, then just
//* paste the contents into some custom text trigger in your map.
//*
//**************************************************************************************************
//==================================================================================================
globals
private sound error
endglobals
//====================================================================================================
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
//call StartSound( error ) //apparently the bug in which you play a sound for the first time
//and it doesn't work is not there anymore in patch 1.22
endfunction
endlibrary