Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ZVSE2
- ; Author: Berserker aka EtherniDee
- ; Version: 2.0
- ; Engine: ERM 2.0+
- ; Requires: ERA 3.0.5+
- ; === TYPES ===
- ; TFactions = array ? of TFaction;
- ; Represents all registered factions, where each index is real faction ID + 1 (thus Neutrals are 0).
- ; TFaction = array ? of MonId: integer;
- ; Represents all registered monsters of single faction. Neutrals with speicifed shadow faction are treated
- ; as real faction creatures. For instance, if neutral Peasant has shadow faction TOWN_CASTLE, then it will
- ; be treated as TOWN_CASTLE member, though preserving it's neutral background/morale and attributes.
- ; TNeutrals = record
- ; Stacks: array 7 of record
- ; MonType: integer;
- ; Quantity: integer; calculated real number of monsters
- ; end;
- ;
- ; SpreadMask: integer; Single bit for each 0..6 stack. If true, stack should be spread among free slots.
- ; end;
- ;
- ; Contains mixed neutrals, that human attacker will face in battle.
- ; TNeutralsConfig = record
- ; Stacks: array 7 of record
- ; MonType: integer;
- ; RelativeQuantity: float; multiplier of total monsters power, used to change final stack quantity for humans
- ; end;
- ;
- ; SpreadMask: integer; Single bit for each 0..6 stack. If true, stack should be spread among free slots.
- ;
- ; Contains mixed neutrals configuration for adventure map tile.
- ; Each stack quantity will be calculated as TotalFightValue * RelativeQuantity / FightValue of particular monster type
- ; ===== PUBLIC =====
- !#VRi^mix_on^:S(TRUE); global mod flag for other mods
- !#VRi^mix_numNeutralStacks^:S3; Number of mixed neutral stacks in battle. FIXME/DELEME in the next versions
- !#VRi^mix_neutralStackScale^:S2; Multiplier for original neutrals quantity on adventure map. FIXME/DELEME in the next versions
- !#VRi^mix_netralStackScaleForHuman^:S2; Additional multiplier of neutrals power only for humans. FIXME/DELEME in the next versions
- ; === END PUBLIC ===
- !#SN:M(M_AUTO_ID)/0/(M_INT)/(M_STORED)/?i^mix_factionAllies^;
- !#VRi^mix_monsRegistrationState^:S0; // ????
- !#VRi^mix_registerRegularMonsters^:S(TRUE); // ????
- !#VRi^mix_moveNeutralsToFactions^:S(TRUE); // ????
- ! !FU(min_GenerateFactionMonster):P;
- ! !SN:Vi^mix_fractionTables^/(MIX_FACTION_CONFLUX)/?(mons:y) V(mons)/14/?(monType:y);
- ! !IF:M^%(monType)^;
- !?FU(OnAfterErmInstructions)&i^mix_moveNeutralsToFactions^<>(FALSE);
- !!MA:O(MON_GOLD_GOLEM)/(TOWN_TOWER);
- !!MA:O(MON_DIAMOND_GOLEM)/(TOWN_TOWER);
- !!MA:O(MON_AZURE_DRAGON)/(TOWN_TOWER);
- !!MA:O(MON_CRYSTAL_DRAGON)/(TOWN_CONFLUX);
- !!MA:O(MON_FAERIE_DRAGON)/(TOWN_RAMPART);
- !!MA:O(MON_RUST_DRAGON)/(TOWN_FORTRESS);
- !!MA:O(MON_ENCHANTER)/(TOWN_TOWER);
- !!MA:O(MON_SHARPSHOOTER)/(TOWN_RAMPART);
- !!MA:O(MON_HALFLING)/(TOWN_CASTLE);
- !!MA:O(MON_PEASANT)/(TOWN_CASTLE);
- !!MA:O(MON_BOAR)/(TOWN_STRONGHOLD);
- !!MA:O(MON_MUMMY)/(TOWN_NECROPOLIS);
- !!MA:O(MON_NOMAD)/(TOWN_STRONGHOLD);
- !!MA:O(MON_ROGUE)/(TOWN_CASTLE);
- !!MA:O(MON_TROLL)/(TOWN_STRONGHOLD);
- !!re i/(MON_SUPREME_ARCHANGEL)/(MON_SACRED_PHOENIX):;
- !!VR(faction:y):Si -(MON_SUPREME_ARCHANGEL);
- !!MA:Oi/(faction);
- !!en:;
- !!MA:O(MON_GHOST)/(TOWN_NECROPOLIS);
- !!re i/(MON_FIRE_MESSENGER)/(MON_WATER_MESSENGER):;
- !!MA:Oi/(TOWN_CONFLUX);
- !!en:;
- !!MA:O(MON_GORYNYCH)/(TOWN_FORTRESS);
- !!MA:O(MON_WAR_ZEALOT)/(TOWN_CASTLE);
- !!MA:O(MON_ARCTIC_SHARPSHOOTER)/(TOWN_TOWER);
- !!MA:O(MON_LAVA_SHARPSHOOTER)/(TOWN_INFERNO);
- !!MA:O(MON_NIGHTMARE)/(TOWN_NECROPOLIS);
- !!MA:O(MON_SANTA_GREMLIN)/(TOWN_TOWER);
- !!MA:O(MON_SYLVAN_CENTAUR)/(TOWN_RAMPART);
- !!MA:O(MON_SORCERESS)/(TOWN_CONFLUX);
- !!MA:O(MON_WEREWOLF)/(TOWN_STRONGHOLD);
- !!MA:O(MON_HELL_STEED)/(TOWN_INFERNO);
- !!MA:O(MON_DRACOLICH)/(TOWN_NECROPOLIS);
- !?FU(mix_OnRegisterRegularMonsters)&i^mix_registerRegularMonsters^<>(FALSE);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_PIKEMAN)/(MON_MAGIC_ELEMENTAL);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_PIKEMAN)/(MON_MAGIC_ELEMENTAL);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_ICE_ELEMENTAL)/(MON_ICE_ELEMENTAL);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_MAGMA_ELEMENTAL)/(MON_MAGMA_ELEMENTAL);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_STORM_ELEMENTAL)/(MON_STORM_ELEMENTAL);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_ENERGY_ELEMENTAL)/(MON_TROLL);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_FIRE_MESSENGER)/(MON_SANTA_GREMLIN);
- !!FU(mix_RegisterRegularMonsterRange):P(MON_SYLVAN_CENTAUR)/(MON_DRACOLICH);
- !?FU(mix_OnRegisterRegularMonsters));
- !!FU:E;
- !!if&v1<>v1:;
- !!FU(ConstructBitMask):P7/0/3/?(mask:y);
- !!IF:M^Mask: %(mask)^;
- !!SN:M(M_AUTO_ID)/0/(M_INT)/(M_TRIGGER_LOCAL)/?(list:y);
- !!FU(DeconstructBitMask):P(mask)/(list);
- !!FU(Array_Join):P(list)/^ ^;
- !!IF:M^Bits: %s(result)^;
- !!FU(mix_SetFactionAllies):P(TOWN_RAMPART)/(TOWN_NECROPOLIS)/50/(TOWN_CONFLUX)/25;
- !!FU(mix_GetRandomBaseOrAlliedFaction):P(TOWN_TOWER)/?(fac1:y);
- !!FU(mix_GetRandomBaseOrAlliedFaction):P(TOWN_TOWER)/?(fac2:y);
- !!FU(mix_GetRandomBaseOrAlliedFaction):P(TOWN_TOWER)/?(fac3:y);
- !!IF:M^%(fac1) %(fac2) %(fac3)^;
- !!SN:Vi^mix_factionAllies^/3/?(towerAllies:y);
- !!FU(Array_Join):P(towerAllies)/^ ^;
- !!IF:M^%s(result)^;
- !!FU:E;
- !!en;
- !!FU(mix_SetFactionAllies):P(TOWN_RAMPART)/(TOWN_NECROPOLIS)/50/(TOWN_CONFLUX)/25;
- !!SN:Mi^mix_factions^/2/?(faction:y);
- !!FU(Array_SortedUnique):P(faction);
- !!FU(Array_Join):P(faction)/^ ^;
- !!IF:M^%s(result)^;
- !!FU(Array_Join):Pi^mix_monToFactionInd^/^ ^;
- !!IF:M^%s(result)^;
- !!FU(mix_GenerateRandomMonster):P?(mon1:y);
- !!FU(mix_GenerateRandomMonster):P?(mon2:y);
- !!FU(mix_GenerateRandomMonster):P?(mon3:y);
- !!IF:M^%(mon1) %(mon2) %(mon3)^;
- !!FU(mix_GenerateFactionMonster):P(TOWN_RAMPART)/(MIX_DISALLOW_ALLIED_FACTIONS)/?(mon1:y);
- !!FU(mix_GenerateFactionMonster):P(TOWN_RAMPART)/(MIX_DISALLOW_ALLIED_FACTIONS)/?(mon2:y);
- !!FU(mix_GenerateFactionMonster):P(TOWN_RAMPART)/(MIX_DISALLOW_ALLIED_FACTIONS)/?(mon3:y);
- !!IF:M^%(mon1) %(mon2) %(mon3)^;
- !?FU(mix_RegisterRegularMonsterRange);
- !#VA(firstMonId:x); The first monster ID.
- !#VA(lastMonId:x); The last monster ID.
- !!if&i^mix_monsRegistrationState^<>(MIX_MONS_ARE_REGISTERING):;
- !!SN:F^ShowErmError^/^The "mix_RegisterRegularMonsterRange" function can be called in "mix_OnRegisterRegularMonsters" event only^;
- !!FU:E;
- !!en;
- !!if|(firstMonId)<(MON_FIRST)/(lastMonId)<(MON_FIRST)/(firstMonId)>(lastMonId)/(lastMonId)>(MIX_MAX_POSSIBLE_MON_ID):;
- !!SN:F^ShowErmError^/^Invalid monsters range to register: %(firstMonId)..%(lastMonId)^;
- !!FU:E;
- !!en;
- ; Ensure, there is enough space in monster-to-faction table
- !!VR(minMonTableSize:y):S(lastMonId) +1;
- !!VR(monToFactionInd:y):Si^mix_monToFactionInd^;
- !!FU(Array_EnsureMinSize):P(monToFactionInd)/(minMonTableSize)/(MIX_UNSET_FACTION);
- ; Start with already registered factions
- !!VR(factions:y):Si^mix_factions^;
- !!SN:M(factions)/?(numFactions:y);
- ; Process each monster in the range
- !!re (mon:y)/(firstMonId)/(lastMonId):;
- ; Do not register same monster twice
- !!SN:V(monToFactionInd)/(mon)/?(factionInd:y);
- !!co&(factionInd:y)<>(MIX_UNSET_FACTION):;
- ; Get monster faction and convert it to index
- !!VR(factionInd:y):S(INT_MIN);
- !!MA:O(mon)/?(factionInd);
- !!FU&(factionInd)=(INT_MIN):E;
- !!VR(factionInd):-(NO_TOWN);
- ; Save monster faction index in monster to faction index map
- !!SN:V(monToFactionInd)/(mon)/(factionInd);
- ; Allocate new factions if accessing faction with new index
- !!if&(factionInd)>=(numFactions):;
- !!VR(newNumFactions:y):S(factionInd) +1;
- !!SN:M(factions)/(newNumFactions);
- !!re i/(numFactions)/(newNumFactions)/1/-1:;
- !!SN:M(M_AUTO_ID)/0/(M_INT)/(M_STORED)/?(faction:y) V(factions)/i/(faction);
- !!en:;
- !!VR(numFactions):S(newNumFactions);
- !!en;
- ; Add monster to specified faction
- !!SN:V(factions)/(factionInd)/?(faction:y) M(faction)/d1 V(faction)/-1/(mon);
- !!VRi^mix_numMons^:+1;
- !!en:; re
- ; mix_RegisterRegularMonsterRange
- !?FU(mix_SetFactionAllies);
- ; Set up to 7 ally factions for specified base faction. They may be used for random monsters generation.
- ; Base faction has weight 100. Each ally is added with some weight. Weight determines the chance to generate
- ; unit from base or any allied factions. For example, if you add Rampart 50 and Neutrals 10 to Castle,
- ; then whe total weight will be 100 + 50 + 10 = 160.
- ; Chance for Castle monster will be 100 / 160 * 100% = 62.5%.
- ; Chance for Rampart monster will be 50 / 160 * 100% = 31.25%.
- ; Chance for Neutrals monster will be 10 / 160 * 100% = 6.25%.
- !#VA(baseFaction:x); Base faction ID to set ally factions for.
- !#VA(variableArgs:x); ... Other arguments are pairs of (Faction ID, Faction Weight).
- !!if|(baseFaction)<(NO_TOWN)/(baseFaction)>(MIX_MAX_POSSIBLE_FACTION_ID):;
- !!SN:F^ShowErmError^/^Invalid "baseFaction" argument: %(baseFaction)^;
- !!FU:E;
- !!en;
- !!VR(baseFactionInd:y):S(baseFaction) -(NO_TOWN);
- ; Check if any ally pair is specified
- !!FU:A?(numArgs:y);
- !!VR(numAllies:y):S(numArgs) -(@variableArgs) +1 :2;
- !!FU&(numAllies)<=0:E;
- ; Get base faction allies array
- !!VR(factionAllies:y):Si^mix_factionAllies^;
- !!SN:M(factionAllies)/?(numFactions:y);
- !!if&(baseFactionInd)>=(numFactions):;
- !!VR(numFactions):S(baseFactionInd) +1;
- !!SN:M(factionAllies)/(numFactions);
- !!en;
- !!SN:M(factionAllies)/(baseFactionInd)/?(baseFactionAllies:y);
- !!if&(baseFactionAllies)=0:;
- !!SN:M(M_AUTO_ID)/0/(M_INT)/(M_STORED)/?(baseFactionAllies:y) V(factionAllies)/(baseFactionInd)/(baseFactionAllies);
- !!el:;
- !!SN:M(baseFactionAllies)/0;
- !!en;
- ; Start filling the array with specified factions
- !!VR(lastArgInd:y):S(numAllies) -1 *2 +(@variableArgs);
- !!VR(totalWeight:y):S(MIX_BASE_FACTION_WEIGHT);
- !!re i/(@variableArgs)/(lastArgInd)/2:;
- !!VR(allyFaction:y):Sxi;
- !!VR(weightArgInd:y):Si +1;
- !!VR(allyFactionWeight:y):Sx(weightArgInd);
- !!if|(allyFaction)<(NO_TOWN)/(allyFaction)>(MIX_MAX_POSSIBLE_FACTION_ID):;
- !!SN:F^ShowErmError^/^Invalid ally faction ID: %(allyFaction)^;
- !!FU:E;
- !!en;
- !!if|(allyFactionWeight)<=0/(allyFactionWeight)>1000000:;
- !!SN:F^ShowErmError^/^Invalid ally faction weight: %(allyFactionWeight)^;
- !!FU:E;
- !!en;
- !!VR(allyFactionInd:y):S(allyFaction) -(NO_TOWN);
- !!VR(totalWeight):+(allyFactionWeight);
- !!SN:M(baseFactionAllies)/d2 V(baseFactionAllies)/-2/(allyFactionInd)/(allyFactionWeight);
- !!en:;
- ; Save total weight in the last allies array
- !!SN:M(baseFactionAllies)/d1 V(baseFactionAllies)/-1/(totalWeight);
- !?FU(mix_GetRandomBaseOrAlliedFaction);
- ; Returns either base faction ID or random allied faction ID, based on faction allies configuration.
- !#VA(baseFaction:x); Base faction ID.
- !#VA(result:x); Result.
- !!VR(result):S(baseFaction);
- !!FU|(baseFaction)<(NO_TOWN)/(baseFaction)>(MIX_MAX_POSSIBLE_FACTION_ID):E;
- !!VR(baseFactionInd:y):S(baseFaction) -(NO_TOWN);
- !!VR(factionAllies:y):Si^mix_factionAllies^;
- !!SN:M(factionAllies)/?(numFactions:y);
- !!FU&(baseFactionInd)>=(numFactions):E;
- !!SN:V(factionAllies)/(baseFactionInd)/?(baseFactionAllies:y);
- !!FU&(baseFactionAllies)=0:E;
- !!SN:M(baseFactionAllies)/?(numAllies:y) V(baseFactionAllies)/-1/?(totalWeight:y);
- !!VR(numAllies):-1 :2;
- !!VR(randomWeight:y):R0/1/(totalWeight);
- !!FU&(randomWeight)<=(MIX_BASE_FACTION_WEIGHT):E;
- !!VR(weight:y):S(MIX_BASE_FACTION_WEIGHT);
- !!re i/0/(numAllies)/1/-1:;
- !!VR(allyDataPtr:y):Si *2;
- !!SN:V(baseFactionAllies)/(allyDataPtr)/?(allyFactionInd:y)/?(allyFactionWeight:y);
- !!VR(weight):+(allyFactionWeight);
- !!br&(weight)>=(randomWeight):;
- !!en:;
- !!VR(result):S(allyFactionInd) +(NO_TOWN);
- !?FU(mix_GenerateRandomMonster);
- ; Generates regular random monster of any faction and returns its ID or NO_MON on error.
- !#VA(result:x);
- !!VR(result):S(NO_MON);
- !!VR(numMons:y):Si^mix_numMons^;
- !!FU&(numMons)<=0:E;
- !!VR(factions:y):Si^mix_factions^;
- !!SN:M(factions)/?(numFactions:y);
- !!VR(monInd:y):R0/1/(numMons) -1;
- !!VR(factionStartMonInd:y):S0;
- !!re i/0/(numFactions)/1/-1:;
- !!SN:V(factions)/i/?(faction:y);
- !!SN:M(faction)/?(numFactionMons:y);
- !!VR(factionEndMonInd:y):S(factionStartMonInd) +(numFactionMons);
- !!if&(monInd)<(factionEndMonInd):;
- !!VR(monInd):-(factionStartMonInd);
- !!SN:V(faction)/(monInd)/?(result);
- !!FU:E;
- !!en;
- !!VR(factionStartMonInd:y):S(factionEndMonInd);
- !!en:;
- !?FU(mix_GenerateFactionMonster);
- ; Generates random monster from particular faction or even allied factions.
- !#VA(faction:x); Faction/Town ID or (NO_TOWN) for neutrals.
- !#VA(allowAlliedFactions:x); Allow to generate from allied factions (TRUE) or not (FALSE). Default: (FALSE).
- !#VA(result:x); Result monster ID or (NO_MON) on error.
- !!VR(result):S(NO_MON);
- !!VR(numMons:y):Si^mix_numMons^;
- !!FU&(numMons)<=0:E;
- ; Get final faction ID and index
- !!FU(mix_GetRandomBaseOrAlliedFaction)&(allowAlliedFactions)<>(FALSE):P(faction)/?(faction);
- !!VR(factionInd:y):S(faction) -(NO_TOWN);
- ; Check if there exists such faction with at least single creature
- !!VR(factions:y):Si^mix_factions^;
- !!SN:M(factions)/?(numFactions:y);
- !!FU|(factionInd)<0/(factionInd)>=(numFactions):E;
- !!SN:V(factions)/(factionInd)/?(factionMons:y);
- !!SN:M(factionMons)/?(numFactionMons:y);
- !!FU&(numFactionMons)<=0:E;
- ; Write random faction monster ID to result
- !!VR(monInd:y):R0/1/(numFactionMons) -1;
- !!SN:V(factionMons)/(monInd)/?(result);
- ; Reset settings of regular monster
- !?FU(mix_ResetRegularMonsterSettings)&i^mix_initedMonSettings^=1;
- !!VRi^mix_initedMonSettings^:S0;
- !!VR(factions:y):Si^mix_factions^;
- !!SN:M(factions)/?(numFactions:y);
- !!re i/0/(numFactions)/1/-1:;
- !!SN:V(factions)/0/?(faction:y);
- !!SN:M(faction);
- !!en:;
- !!SN:Mi^mix_factions^ Mi^mix_factionAllies^;
- !?FU(OnAdvMapTileHint)&y1<>y1; DISABLED, better see unscaled value in current implementation
- ; WHY????
- !#VA(x:x) (y:x) (z:x);
- !!OB(x)/(y)/(z):T?(objType:y) U?(monType:y);
- !!FU&(objType)<>(OBJ_MONSTER):E;
- !!FU(mix_GetTileNeutralsConfig):P(x)/(y)/(z)/?(config:y);
- !!FU&(config)=0:E;
- !!SN:H^monname^/(monType)/1/?(monName:z);
- !!MO(x)/(y)/(z):G?(monNum:y);
- !!VR(monNum):*i^mix_netralStackScaleForHuman^;
- !!VR(monNum)&(monNum)<0:S(INT32_MAX);
- !!FU(mix_MonQtyToFuzzyText):P(monNum);
- !!MM:M^%(monName): %S(result)^;
- !?FU(OnAdventureMapRightMouseClick)&y1<>y1; DEBUG CODE
- !!CM:R0 P?(x:y)/?(y:y)/?(z:y);
- !!FU(mix_GetTileNeutralsConfig):P(x)/(y)/(z)/?(config:y)/(MIX_AUTOCREATE_NEUTRALS_CONFIG);
- !#VA(relQts[3]:e);
- !!VR(relQts[0]):S333 :100;
- !!VR(relQts[1]):S333 :100;
- !!VR(relQts[2]):S333 :100;
- !!SN:V(config)/0/2/(relQts[0])/9/(relQts[1])/13/(relQts[2]);
- !!SN:M(M_AUTO_ID)/(MIX_NEUTRALS_STRUCT_SIZE)/(M_INT)/(M_TRIGGER_LOCAL)/?(neutrals:y);
- !!FU(mix_ReadTileNeutrals):P(x)/(y)/(z)/(neutrals);
- !#VA(monTypes[3]:y) (monNums[3]:y);
- !!SN:V(neutrals)/0/?(monTypes[0])/?(monNums[0])/?(monTypes[1])/?(monNums[1])/?(monTypes[2])/?(monNums[2]);
- !!IF:M^%(monTypes[0])/%(monNums[0]) %(monTypes[1])/%(monNums[1]) %(monTypes[2])/%(monNums[2])^;
- !?FU(OnAdventureMapRightMouseClick);
- !!CM:P?(x:y)/?(y:y)/?(z:y);
- !!OB(x)/(y)/(z):T?(objType:y);
- !!FU&(objType)<>(OBJ_MONSTER):E;
- !!SN:M(M_AUTO_ID)/(MIX_NEUTRALS_STRUCT_SIZE)/(M_INT)/(M_TRIGGER_LOCAL)/?(neutrals:y);
- !!FU(mix_ReadTileNeutrals):P(x)/(y)/(z)/(neutrals);
- !!SN:V(neutrals)/0/?(firstStackType:y);
- !!FU&(firstStackType)=(NO_MON):E;
- !!VR(showFuzzyNumbers:y):S(TRUE);
- !!VR(disablePopup:y):S(FALSE);
- !!FU(mix_OnViewNeutrals):P?(showFuzzyNumbers)/?(disablePopup)/(x)/(y)/(z);
- !!FU&(disablePopup)<>(FALSE):E;
- !!CM:R0;
- !!VR(text:z):S^^;
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- !!VR(pairInd:y):Si *2;
- !!SN:V(neutrals)/(pairInd)/?(monType:y)/?(monNum:y);
- !!if&(monType)<>(NO_MON):;
- !!SN:H^monname^/(monType)/1/?(monName:z);
- !!VR(monNumStr:z):S^%(monNum)^;
- !!if&(showFuzzyNumbers)<>(FALSE):;
- !!FU(mix_MonQtyToFuzzyText):P(monNum);
- !!VR(monNumStr):Ss^result^;
- !!en;
- !!VR(text):+^{%(monName)}: %(monNumStr)
- ^;
- !!en;
- !!en:;
- !!IF:M1/4/(text);
- !?FU(mix_MonQtyToFuzzyText);
- !#VA(monNum:x);
- ; Returns result in s^result^
- !!if&(monNum)<=4:;
- !!VRs^result^:S^1-4^;
- !!el&(monNum)<=9:;
- !!VRs^result^:S^5-9^;
- !!el&(monNum)<=19:;
- !!VRs^result^:S^10-19^;
- !!el&(monNum)<=49:;
- !!VRs^result^:S^20-49^;
- !!el&(monNum)<=99:;
- !!VRs^result^:S^50-99^;
- !!el&(monNum)<=249:;
- !!VRs^result^:S^100-249^;
- !!el&(monNum)<=499:;
- !!VRs^result^:S^250-499^;
- !!el&(monNum)<=999:;
- !!VRs^result^:S^500-999^;
- !!el&(monNum)<1000000:;
- !!VR(thousandInd:y):S(monNum) :1000;
- !!FU(mix_IntLog2):P(thousandInd)/?(thousandPower:y);
- !!VR(borderValue:y):S1 Sd<<(thousandPower) *1000 -1;
- !!VR(thousandPower)&(monNum)<=(borderValue):-1;
- !!VR(rangeStart:y):S1 Sd<<(thousandPower);
- !!VRs^result^:S^%(rangeStart)K+^;
- !!el:;
- !!VR(billionInd:y):S(monNum) :1000000;
- !!FU(mix_IntLog2):P(billionInd)/?(billionPower:y);
- !!VR(borderValue:y):S1 Sd<<(billionPower) *1000000 -1;
- !!VR(borderValue)&(borderValue)<0:S(INT32_MAX);
- !!VR(billionPower)&(monNum)<=(borderValue):-1;
- !!VR(rangeStart:y):S1 Sd<<(billionPower);
- !!VRs^result^:S^%(rangeStart)M+^;
- !!en;
- !?FU(mix_IntLog2);
- ; Returns Ceil(Log2(N))
- !#VA(value:x) (result:x);
- !!VR(result):S0;
- !!FU&(value)<=0:E;
- !!VR(testValue:y):S1;
- !!re i:;
- !!br|(testValue)>=(value)/(testValue)<0:;
- !!VR(result):+1;
- !!VR(testValue):Sd<<1;
- !!en:;
- !!VR(result)&(testValue)>1073741824:+1;
- !?FU(OnEveryDay)&i^timerDay^=1/i^timerOnce^=1;
- !!FU(mix_InitializeNeutrals):P;
- !?FU(mix_InitializeNeutrals);
- !!UN:U(OBJ_MONSTER)/(ANY_OBJ)/?(numMapMons:y);
- !!FU&(numMapMons)<=0:E;
- !!VRv2:S-1;
- !!VR(singleStackRelQty:e):S1 *i^mix_netralStackScaleForHuman^ :i^mix_numNeutralStacks^;
- !!re i/1/(numMapMons):;
- !!UN:U(OBJ_MONSTER)/(ANY_OBJ)/-1/2;
- !!OB2:U?(origMonType:y);
- !!MO2:Gd*i^mix_neutralStackScale^;
- !!FU(mix_GetTileNeutralsConfig):Pv2/v3/v4/?(config:y)/(MIX_AUTOCREATE_NEUTRALS_CONFIG);
- !!SN:V(config)/0/(origMonType)/(singleStackRelQty) V(config)/-1/5;
- !!re j/1/i^mix_numNeutralStacks^/1/-1:;
- !!VR(randomMonType:y):S(NO_MON);
- !!FU(mix_GenerateRandomMonster):P?(randomMonType)/(origMonType);
- ; Provide fallback if no user monster generation function is provided
- !!if&(randomMonType)=(NO_MON):;
- !!VR(randomMonType:y):T0/0/140;
- !!VR(randomMonType)&(randomMonType)=122:S141;
- !!VR(randomMonType)&(randomMonType)=124:S142;
- !!VR(randomMonType)&(randomMonType)=126:S143;
- !!VR(randomMonType)&(randomMonType)=128:S144;
- !!en;
- !!VR(pairInd:y):Sj *2;
- !!SN:V(config)/(pairInd)/(randomMonType)/(singleStackRelQty);
- !!en:; re
- !!en:; re
- !?FU(OnBeforeBattleUniversal);
- !!VRi^mix_isNeutralsBattle^:S0;
- !?FU(OnBeforeBattleUniversal)&(ERM_FLAG_REAL_BATTLE);
- !#VA(heroes[2]:y) (owners[2]:y) (ai[2]:y);
- !!BA:O?(owners[0])/?(owners[1]) H?(heroes[0])/?(heroes[1]) P?(x:y)/?(y:y)/?(z:y);
- !!VR(ai):C(TRUE)/(TRUE);
- !!OW&(owners[0])<>(NO_OWNER):I(owners[0])/?(ai[0]);
- !!OW&(owners[1])<>(NO_OWNER):I(owners[1])/?(ai[1]);
- ; We handle only battle of human player vs AI
- !!FU|(ai[0])=(TRUE)/(owners[1])<>(NO_OWNER):E;
- ; Get original monster info, exit if siege or any error
- !!FU(mix_GetAdvMapMonInfo):P(x)/(y)/(z)/?(origMonType:y)/?(origMonNum:y);
- !!FU|(origMonType)=(NO_MON)/(origMonNum)<=0:E;
- ; Calculate total fight value
- !!MA:F(origMonType)/?(origMonFightValue:y);
- !!VR(totalFightValue:e):S(origMonFightValue) *(origMonNum);
- !!FU&(totalFightValue)<=0:E;
- ; Determine concrete neutral stacks for battle (other scripts/mods are totally ignored here)
- !!SN:M(M_AUTO_ID)/(MIX_NEUTRALS_STRUCT_SIZE)/(M_INT)/(M_TRIGGER_LOCAL)/?(neutrals:y);
- !!FU(mix_ReadTileNeutrals):P(x)/(y)/(z)/(neutrals);
- !!SN:V(neutrals)/0/?(firstStackType:y);
- !!FU&(firstStackType)=(NO_MON):E;
- ; Assign stacks and remember total neutrals fight value
- !!VRi^mix_isNeutralsBattle^:S1;
- !!VR(neutralsFightValue:e):S0;
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- !!VR(pairInd:y):Si *2;
- !!SN:V(neutrals)/(pairInd)/?(monType:y)/?(monNum:y);
- !!BA:M(BATTLE_RIGHT)/i/(monType)/(monNum);
- !!if&(monType)<>(NO_MON):;
- !!MA:F(monType)/?(monFightValue:y);
- !!VR(stackFightValue:e):S(monFightValue) *(monNum);
- !!VR(neutralsFightValue):+(stackFightValue);
- !!en;
- !!en:;
- !!VR(neutralsFightValueWrapper:y):C(neutralsFightValue);
- !!VR(neutralsFightValueWrapper):C?i^mix_neutralsFightValue^;
- ; Remember real battle coordinates in case some script tries to manipulate them
- !!VRi^mix_battleX^:S(x);
- !!VRi^mix_battleY^:S(y);
- !!VRi^mix_battleZ^:S(z);
- !?FU(OnBeforeBattleUniversal);
- ; Call custom callback, allowing to log configurated pre-battle armies
- !!FU(mix_OnSetupBattleArmies):P;
- !?FU(OnAfterBattleUniversal)&(ERM_FLAG_REAL_BATTLE)/i^mix_isNeutralsBattle^=(TRUE);
- ; Check if there are neutral stacks left, calculating total fight value
- !!VR(finalFightValue:e):S0;
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- !!BA:M(BATTLE_RIGHT)/i/?(monType:y)/?(monNum:y);
- !!if&(monType)<>(NO_MON)/(monNum)>0:;
- !!MA:F(monType)/?(monFightValue:y);
- !!VR(stackFightValue:e):S(monFightValue) *(monNum);
- !!VR(finalFightValue):+(stackFightValue);
- !!en;
- !!en:;
- ; Call custom callback, allowing to log battle casualities and implement, for instance, custom necromancy
- !!FU(mix_OnAnalyseBattleResults):P;
- ; Exit if all neutrals are dead and clear neutrals config
- !!if&(finalFightValue)<=0:;
- !!FU(mix_ClearTileNeutralsConfig):Pi^mix_battleX^/i^mix_battleY^/i^mix_battleZ^;
- !!FU:E;
- !!en;
- ; Recalculate new neutrals quantity on map as Ceil(Original quantity * Final Fight Value / Original Fight Value)
- !!FU(mix_GetAdvMapMonInfo):Pi^mix_battleX^/i^mix_battleY^/i^mix_battleZ^/?(origMonType:y)/?(origMonNum:y);
- !!VR(originalFightValueWrapper:y):Si^mix_neutralsFightValue^;
- !!VR(originalFightValueWrapper):C?(originalFightValue:e);
- !!VR(newAdvMapMonQtyFloat:e):S(finalFightValue) :(originalFightValue) *(origMonNum);
- !!VR(ceiler:e):S999 :1000;
- !!VR(newAdvMapMonQtyFloat):+(ceiler);
- !!VR(newAdvMapMonQtyFloat)&(newAdvMapMonQtyFloat)>=(INT32_MAX):S(INT32_MAX);
- !!VR(newMonNum:y):S(newAdvMapMonQtyFloat);
- !!if&(newMonNum)=0:;
- ; New number cannot reach zero
- !!VR(newMonNum):S1;
- !!el&(newMonNum)<0:;
- ; Handle integer overflow
- !!VR(newMonNum):S(INT32_MAX);
- !!en;
- ; New number cannot be bigger, than original one if neutrals have some possibility to grow during battle
- !!VR(newMonNum)&(newMonNum)>(origMonNum):S(origMonNum);
- ; Call custom callback, allowing to change final neutral stack size on adventure map
- ; Here custom rules may be implement, for intstance, restoring neutrals if attacker escaped or died
- !!FU(mix_OnApplyNeutralsCasualities):P?(newMonNum)/(origMonNum)/i^mix_battleX^/i^mix_battleY^/i^mix_battleZ^;;
- ; Overwrite neutrals final army
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- !!BA:M(BATTLE_RIGHT)/i/(NO_MON)/0;
- !!en:;
- !!BA:M(BATTLE_RIGHT)/0/(origMonType)/(newMonNum);
- !?FU(mix_GetAdvMapMonInfo);
- ; Given coordinates of monster on adventure map, returns monster type and number.
- ; If object is not a monster, (NO_MON) type is returned.
- !#VA(x:x) (y:x) (z:x);
- !#VA(monType:x); OUT, (NO_MON) on error
- !#VA(monNum:x); OUT
- !!VR(monType):S(NO_MON);
- !!VR(monNum):S0;
- !!OB(x)/(y)/(z):T?(objType:y) U?(monType);
- !!if&(objType)<>(OBJ_MONSTER):;
- !!VR(monType):S-1;
- !!FU:E;
- !!en:;
- !!MO(x)/(y)/(z):G?(monNum);
- !?FU(mix_ClearTileNeutralsConfig);
- !#VA(x:x) (y:x) (z:x);
- !!VR(configKey:z):S^mix_mon_%(x)_%(y)_%(z)^;
- !!SN:W(configKey)/?(config:y);
- !!SN&(config)<>0:M(config) W(configKey)/0;
- !?FU(mix_GetTileNeutralsConfig);
- !#VA(x:x) (y:x) (z:x) (result:x) (autocreate:x) = (FALSE);
- ; Returns TNeutralsConfig: existing config for given map tile in the form of SN:M array ID or 0 if config is missing.
- ; Specify (autocreate) = (TRUE) to create new config, if nothing is found.
- ; Warning: calling any Mixed Neutrals API may invalid the result array ID.
- !!VR(configKey:z):S^mix_mon_%(x)_%(y)_%(z)^;
- !!SN:W(configKey)/?(result);
- ; No config found, autocreate empty
- !!SN&(result)=0/(autocreate)<>(FALSE):M(M_AUTO_ID)/(MIX_NEUTRALS_CONFIG_SIZE)/(M_INT)/(M_STORED)/?(result)
- W(configKey)/(result)
- V(result)/0/(NO_MON)/0/(NO_MON)/0/(NO_MON)/0/(NO_MON)/0/(NO_MON)/0/(NO_MON)/0/(NO_MON)/0
- V(result)/(MIX_NEUTRALS_CONFIG_SPREAD_MASK_IND)/0;
- !?FU(mix_ReadTileNeutrals);
- ; Calculates neutrals types and current final quantitites, writing them to user-specified array.
- ; The result contains virtual slots, which may be empty and non-sequential.
- !#VA(x:x) (y:x) (z:x);
- !#VA(neutrals:x); T Neutrals. SN:M array ID to write finally calculated data to.
- !#VA(success:x); Boolean. Optional, OUT. True, if data exists and was successfully read.
- !#VA(leftFightValue:x); Float. Optional, OUT. Not used Fight Value, that should be preserved after battle.
- !!VR(success):S(FALSE);
- ; Get config, exit if none
- !!FU(mix_GetTileNeutralsConfig):P(x)/(y)/(z)/?(config:y);
- !!FU&(config)=(FALSE):E;
- ; Get total number of original creatures
- !!FU(mix_GetAdvMapMonInfo):P(x)/(y)/(z)/?(origMonType:y)/?(totalMons:y);
- !!FU|(origMonType)=(NO_MON)/(totalMons)<=0:E;
- ; Calculate total fight value as float
- !!MA:F(origMonType)/?(origMonFightValue:y);
- !!VR(totalFightValue:e):S(totalMons) *(origMonFightValue);
- !!FU&(totalFightValue)<=0:E;
- ; Initialize result to empty slots
- !!SN:V(neutrals)/0/-1/0/-1/0/-1/0/-1/0/-1/0/-1/0/-1/0;
- ; Copy spread mask to result
- !!SN:V(config)/(MIX_NEUTRALS_CONFIG_SPREAD_MASK_IND)/?(spreadMask:y);
- !!SN:V(config)/(MIX_NEUTRALS_STRUCT_SPREAD_MASK_IND)/(spreadMask);
- ; Unpack spread mask
- !#VA(spreads[7]:y);
- !!VR(spreads):C(FALSE)/(FALSE)/(FALSE)/(FALSE)/(FALSE)/(FALSE)/(FALSE);
- !!re i/(@spreads[0])/(@spreads[-1]):;
- !!VR(spreadInd:y):Si -(@spreads);
- !!VR(testBit:y):S1 Sd<<(spreadInd) &(spreadMask);
- !!VRyi&(testBit)<>0:S(TRUE);
- !!en:;
- ; Calculate each stack size in floats
- !!VR(numStacksToSpread:y):S0;
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- ; Skip free slot
- !!VR(pairInd:y):Si *2;
- !!SN:V(config)/(pairInd)/?(monType:y)/?(monRelQuantity:e);
- !!co&(monType)<=(NO_MON):;
- ; Skip slot with zero fight value or quantity
- !!MA:F(monType)/?(monFightValue:y);
- !!VR(monQuantityFloat:e):S(totalFightValue) *(monRelQuantity) :(monFightValue);
- !!co&(monQuantityFloat)<=0:;
- ; Force integer cap for number of creatures
- !!VR(monQuantityFloat)&(monQuantityFloat)>=(INT32_MAX):S(INT32_MAX);
- ; Adjust number of stacks to spread
- !!VR(spread:y):S(@spreads) +i Sy(spread);
- !!VR(numStacksToSpread)&(spread)=(TRUE):+1;
- ; Save monster in the result list
- !!SN:V(neutrals)/(pairInd)/(monType)/(monQuantityFloat);
- !!en:;
- ; Determine free slot indexes and count
- !#VA(freeSlots[7]:y);
- !!VR(freeSlotPtr:y):S(@freeSlots);
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- !!VR(pairInd:y):Si *2;
- !!SN:V(neutrals)/(pairInd)/?(monType:y);
- !!if&(monType)=(NO_MON):;
- !!VRy(freeSlotPtr):Si;
- !!VR(freeSlotPtr):+1;
- !!en;
- !!en:;
- !!VR(numFreeSlots:y):S(freeSlotPtr) -(@freeSlots);
- ; Apply spreading
- !!if&(numFreeSlots)>0/(numStacksToSpread)>0:;
- !!VR(slotsPerSpreadedStack:y):S(numFreeSlots) :(numStacksToSpread);
- !!VR(freeSlotsReserve:y):S(numFreeSlots) %(numStacksToSpread);
- !!re i/0/(MIX_MAX_NEUTRAL_STACKS)/1/-1:;
- ; Continue if slot is free
- !!VR(pairInd:y):Si *2;
- !!SN:V(neutrals)/(pairInd)/?(monType:y)/?(monQuantityFloat:e);
- !!co&(monType)=(NO_MON):;
- ; Continue if slot must not be spreaded
- !!VR(spread:y):S(@spreads) +i Sy(spread);
- !!co&(spread)<>(TRUE):;
- ; Determine, how many free slots will be used for current stack
- !!VR(numOccupiedSlots:y):S(slotsPerSpreadedStack);
- !!if&(freeSlotsReserve)>0:;
- !!VR(freeSlotsReserve):-1;
- !!VR(numOccupiedSlots):+1;
- !!en;
- !!co&(numOccupiedSlots)<=0:;
- ; Calculate float quantity for each slot to occupy
- !!VR(numStackSlots:y):S(numOccupiedSlots) +1;
- !!VR(spreadedMonQuantityFloat:e):S(monQuantityFloat) :(numStackSlots);
- ; Update float monster quantity in main stack slot
- !!SN:V(neutrals)/(pairInd)/?t/(spreadedMonQuantityFloat);
- ; Occupy new slots
- !!re j/0/(numOccupiedSlots)/1/-1:;
- !!VR(freeSlot:y):S(@freeSlots) +j Sy(freeSlot);
- !!VR(pairInd:y):S(freeSlot) *2;
- !!SN:V(neutrals)/(pairInd)/(monType)/(spreadedMonQuantityFloat);
- !!en:; re
- !!en:; re
- !!en; if
- ; Apply merging in order to obtain integer non-zero quantities for each slot and unused fight value sum
- !#VA(monReserve[14]:y); array 7 of (monType: int, monQuantity: float)
- !!VR(monReserveSize:y):S0;
- !!VR(numRealSlots:y):S0;
- !!VRi:S(MIX_MAX_NEUTRAL_STACKS) -1;
- ; Process each stack from right to left
- !!re i/i/0/-1:;
- ; Continue if slot is free
- !!VR(pairInd:y):Si *2;
- !!SN:V(neutrals)/(pairInd)/?(monType:y)/?(monQuantityFloat:e) V(neutrals)/(pairInd)/(NO_MON)/0;
- !!co&(monType)=(NO_MON):;
- ; Search for the same monster type in reserve
- !!re (monReserveInd:y)/0/(monReserveSize)/1/-1:;
- !!VR(monReservePtr:y):S(monReserveInd) *2 +(@monReserve);
- !!VRy(monReservePtr):C?(reserveMonType:y)/?(reserveMonQuantity:e);
- !!br&(reserveMonType)=(monType):;
- !!en:;
- ; Add monster from reserve to current stack, if any
- ; Create new reserve if current monster type is absent
- !!if&(monReserveInd)<(monReserveSize):;
- !!VR(monQuantityFloat):+(reserveMonQuantity);
- !!el:;
- !!VR(monReservePtr):S(monReserveSize) *2 +(@monReserve);
- !!VR(monReserveSize):+1;
- !!en;
- ; Convert current stack quantity to integer
- !!VR(monQuantity:y):S(monQuantityFloat);
- !!VR(monQuantity)&(monQuantity)<0:S(INT32_MAX);
- ; Calculate rest monster quantity for reserve
- !!VR(reserveMonQuantity:e):S(monQuantityFloat) -(monQuantity);
- ; Update reserve
- !!VRy(monReservePtr):C(monType)/(reserveMonQuantity);
- ; Update result stack
- !!VR(monType)&(monQuantity)=0:S(NO_MON);
- !!SN:V(neutrals)/(pairInd)/(monType)/(monQuantity);
- !!en:; re
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement