Name | Type | is_array | initial_value |
ab_descrs | string | Yes | |
ab_extraMana | integer | No | |
ab_extraParamDescr | string | Yes | |
ab_extraParamDescrInt | string | Yes | |
ab_extraParamIntAbout1level | boolean | Yes | |
ab_extraParamKeys | integer | Yes | |
ab_extraParamKeysInt | integer | Yes | |
ab_extraParams | integer | No | |
ab_extraParamsInt | integer | No | |
ab_extraParamValues | real | Yes | |
ab_extraParamValuesInt | integer | Yes | |
ab_maxLevel | integer | No | |
act_monsterStats | real | Yes | |
act_number | integer | No | 2 |
AI_hero_skills_from | integer | Yes | |
AI_hero_skills_per_levels | abilcode | Yes | |
AI_hero_skills_to | integer | Yes | |
any_player_hero | unit | Yes | |
assign_multiboard | multiboard | No | |
assigs | integer | No | |
assigs_abilInTavern | ability | Yes | |
assigs_curType | integer | Yes | |
assigs_goldReward | integer | Yes | |
assigs_quest | quest | Yes | |
assigs_questMark1 | questitem | Yes | |
assigs_reputationReward | integer | Yes | |
assigs_status | integer | Yes | |
assigs_t1_curCount | integer | Yes | |
assigs_t1_needCount | integer | Yes | |
assigs_t1_unitFamily | integer | Yes | |
b18_abilBuy | abilcode | Yes | |
b18_abilBuy_heroNumber | integer | Yes | |
b18_abilBuy_player | player | Yes | |
b18_db_goldCosts | integer | Yes | |
b18_db_maxByeHeroes | integer | No | |
b18_db_reputationsCosts | integer | Yes | |
b18_heroes | unit | Yes | |
b19_selectedModeIds | string | Yes | |
C | integer | No | |
ch_building_rand_unType | integer | No | |
chunk_actual_monster_count | integer | Yes | |
chunk_actual_monster_index | integer | Yes | |
chunk_actual_monster_level | integer | Yes | |
chunk_actual_monster_name | string | Yes | |
chunk_actual_monster_type | unitcode | Yes | |
chunk_actual_monsters | integer | No | |
chunk_colors_layer | string | Yes | |
chunk_colors_layer_unit | playercolor | Yes | |
chunk_completed | boolean | Yes | true |
chunk_creeps_chunk | group | Yes | |
chunk_creeps_group | group | No | |
chunk_decRegion | rect | No | |
chunk_exit_goLayer | integer | Yes | |
chunk_exit_location | integer | Yes | |
chunk_exit_regionBuildDepth2 | rect | Yes | |
chunk_exit_textureHeightDepth2 | real | Yes | |
chunk_exit_textureWidthDepth2 | real | Yes | |
chunk_exit_unit | unit | Yes | |
chunk_exit_unitDepth2 | unit | Yes | |
chunk_exit_xBuildDepth2 | real | Yes | |
chunk_exit_yBuildDepth2 | real | Yes | |
chunk_gen_A | integer | No | |
chunk_gen_B | integer | No | |
chunk_gen_needMonsters | boolean | No | |
chunk_gen_point_placed | location | No | |
chunk_gen_selected_location | integer | No | |
chunk_gen_unType | unitcode | No | |
chunk_go_deltaX | real | No | |
chunk_go_deltaY | real | No | |
chunk_go_directional | integer | No | |
Chunk_go_fo_player_player | player | No | |
Chunk_go_for_player_position | location | No | |
chunk_go_startX | real | No | |
chunk_go_startY | real | No | |
chunk_go_unitNumber | integer | No | |
chunk_go_units | group | No | |
chunk_go_unitTotal | integer | No | |
chunk_layer_mob_countFrom | hashtable | No | |
chunk_layer_mob_countTo | hashtable | No | |
chunk_layer_mob_levelFrom | hashtable | No | |
chunk_layer_mob_levelTo | hashtable | No | |
chunk_layer_mobSlots | integer | Yes | |
chunk_layers | integer | No | |
chunk_level | integer | Yes | |
chunk_mob_A | integer | No | |
chunk_mob_B | integer | No | |
chunk_mob_placedRegion | rect | No | |
chunk_selected_count | integer | No | |
chunk_selected_extraLocation | integer | No | |
chunk_selected_layer | integer | No | |
chunk_selected_level | integer | No | |
chunk_selected_mobSlot | integer | No | |
chunk_selected_unDbIndex | integer | No | |
chunk_selected_unT | unitcode | No | |
creep_hero_skils | abilcode | Yes | |
creeps_extra | group | No | |
D | integer | No | |
db_units | unitcode | Yes | |
db_unitsLevel_from | integer | Yes | |
db_unitsLevel_to | integer | Yes | |
db_unName | string | Yes | |
enter_number | integer | No | |
enter_offset_for_player_count | integer | Yes | |
EventIndex | integer | No | |
exit_danger | integer | Yes | |
exit_lock | boolean | No | |
exit_specter_buildPos | location | Yes | |
exit_specter_floaText | texttag | Yes | |
exit_specter_positions | location | Yes | |
exit_specter_speceffect | effect | Yes | |
exit_specter_speceffects | integer | No | |
exit_specter_units | group | No | |
exit_specter_unitsBuildings | group | No | |
exit_unitSet_ex_units_from | integer | Yes | |
exit_unitSet_ex_units_to | integer | Yes | |
exit_unitSet_i | integer | No | |
exit_unitSet_level | integer | Yes | |
exit_unitSet_uCount | integer | Yes | |
exit_unitSet_uDbIndex | integer | Yes | |
exit_unitSet_uType | unitcode | Yes | |
exit_unitSets_count | integer | No | |
exitDepth2_active | boolean | No | |
exitDepth2_index | integer | No | |
exitDepth2_layer | integer | Yes | |
exitDepth2_location | integer | Yes | |
exp_by_player_count | real | Yes | 100.00 |
go_to_enters | location | Yes | |
handicap_by_player_count | real | Yes | 100.00 |
hero_abilities | abilcode | Yes | |
hero_icon | string | Yes | |
hero_timer | timer | No | |
hero_timer_window | timerdialog | No | |
inSelectedStory | boolean | No | |
isActiveSelectedMode | boolean | No | |
location_count | integer | No | |
location_finishLocation | trigger | No | |
location_inRandomChoose | boolean | Yes | |
location_name | string | Yes | |
location_specter_building | unitcode | Yes | |
location_triggerPlaced | trigger | Yes | |
location_weight | real | Yes | |
marcer_A | integer | No | |
marcer_B | integer | No | |
marcer_check | boolean | No | |
mercs_all_mercs_group | group | No | |
mercs_player_mercs | group | Yes | |
mercs_reborn_mercs | group | No | |
modesQuest | quest | No | |
opt_number_select | integer | No | |
opt_preloaded_heroes | unitcode | Yes | |
player_all_battle_units | group | No | |
player_ALL_heroes | group | No | |
player_currentLayer | integer | Yes | |
player_extraMeat | integer | Yes | |
player_heroes | group | Yes | |
players_count | integer | No | |
players_group | force | No | |
purgatory_apruve | integer | No | |
purgatoryLeaderboard | multiboard | No | |
purgatoryTimer | timer | No | |
purgatoryTimerWindow | timerdialog | No | |
rareItems_affixAbil | abilcode | Yes | |
rareItems_affixAbil_maxLevel | integer | Yes | |
rareItems_affixies | integer | No | |
rareItems_selectedAffix | integer | No | |
relict_count | integer | No | |
relict_item | itemcode | Yes | |
run_left_steps | integer | No | |
run_multiboard | multiboard | No | |
run_stats_extraXP | integer | Yes | |
run_stats_mercMultDamage | real | Yes | |
run_stats_mercMultHP | real | Yes | |
run_stats_steps | integer | No | |
select_hero | unit | Yes | |
select_hero_armies | group | Yes | |
select_hero_heroes | unitcode | Yes | |
select_hero_heroes_count | integer | No | |
select_hero_positions | location | Yes | |
select_hero_positions_army | location | Yes | |
select_hero_positions_count | integer | No | |
select_hero_souls | group | No | |
select_selectedNumber | integer | No | |
select_selectedPlayer | player | No | |
selected_assign | integer | No | |
selected_exit | integer | No | |
souls_upg_gold | integer | Yes | |
souls_upg_heroes | integer | Yes | |
souls_upg_mercDamage | integer | Yes | |
souls_upg_mercHP | integer | Yes | |
souls_upg_steps | integer | Yes | |
souls_upg_xp | integer | Yes | |
status | integer | No | 1 |
taskwave_abils_abilCode | abilcode | Yes | |
taskwave_enemy_cooldown | real | Yes | |
taskwave_enemy_count | integer | Yes | |
taskwave_enemy_firstDelay | real | Yes | |
taskwave_enemy_from | integer | Yes | |
taskwave_enemy_limit | integer | Yes | |
taskwave_enemy_to | integer | Yes | |
taskwave_enemy_type | unitcode | Yes | |
taskwave_reward_item | item | Yes | |
taskwave_reward_item_text | string | Yes | |
taskwave_rewards_from | integer | Yes | |
taskwave_rewards_to | integer | Yes | |
tavern_abil_assign | abilcode | Yes | |
tavern_assign_abil_gold | integer | Yes | |
tavern_assign_abil_reputation | integer | Yes | |
tavern_assign_abil_t1_count | integer | Yes | |
tavern_assign_abil_t1_family | integer | Yes | |
tavern_assign_abil_type | integer | Yes | |
tavern_selected_abil_assign | integer | No | |
teamCountMercHeroes | integer | No | |
teamReputation | integer | No | |
tech_leadership_extraFood | integer | Yes | |
TestEvent1 | real | No | |
TestEvent2 | real | No | |
TestEvent3 | real | No | |
TestInteger | integer | No | |
TestIntegerArray | integer | Yes | |
tile_type | terraintype | Yes | |
tree_type | destructablecode | Yes | |
tree_variations | integer | Yes | |
trig_createRandBuilding | trigger | Yes | |
trig_createRandBuilding_count | integer | No | |
trig_createRandBuilding_loc | location | No | |
unFamilies | integer | No | |
unFamily_countFrom | integer | Yes | |
unFamily_countTo | integer | Yes | |
unFamily_descr | string | Yes | волки |
unFamily_from | integer | Yes | |
unFamily_icon | string | Yes | волки |
unFamily_name | string | Yes | волки |
unFamily_priceMult | integer | Yes | |
unFamily_reputation | integer | Yes | |
unFamily_to | integer | Yes | |
unFamily_unType | unitcode | Yes | |
WaitForEvent | real | No | |
wave_enemy_group | group | No | |
wave_enemy_player | player | No | |
wave_isActive | boolean | No | |
wave_isComplete | boolean | No | |
wave_left_enemies | integer | No | |
wave_needDef | unit | No | |
wave_reward_item | item | Yes | |
wave_reward_items | integer | No | |
wave_slot_cooldownTimer | timer | Yes | |
wave_slot_isCooldown | boolean | Yes | |
wave_slot_unGroups | group | Yes | |
wave_slot_unLeftReserv | integer | Yes | |
wave_slot_unLimitAlive | integer | Yes | |
wave_slot_unRepeatlCooldown | real | Yes | |
wave_slot_unStartDelay | real | Yes | |
wave_slot_unType | unitcode | Yes | |
wave_start_location | location | Yes | |
wave_start_locations | integer | No | |
wave_target_location | location | No | |
wave_unSlots | integer | No | |
wave_win | boolean | No | |
x | integer | No | |
x2 | integer | No | |
x3 | integer | No | |
x4 | integer | No | |
xAbil | ability | No | |
xAbilCode | abilcode | No | |
xGroup | group | No | |
xItem | item | No | |
xItemType | itemcode | No | |
xItemTypes | itemcode | Yes | |
xLocation | location | No | |
xLocation2 | location | No | |
xMercUnit | unit | No | |
xPlayer | player | No | |
xReal | real | No | |
xString | string | No | |
xString2 | string | No | |
xUnit | unit | No | |
xUnit2 | unit | No | |
xUnitT | unitcode | No |
function DragonTest(unit)
print(1)
local abil = BlzGetUnitAbility(gg_unit_H009_0136, FourCC("ACm3"))
print(2)
-- ABILITY_RLF_MOVEMENT_SPEED_REDUCTION_PERCENT_NSO4
local field = ConvertAbilityRealField( FourCC('A01S:Aegr') )
print(3, BlzGetAbilityRealLevelField(abil, field))
BlzSetAbilityRealFieldBJ(abil, field, 0.5)
print(4, BlzGetAbilityRealField(abil, field))
--BlzSetAbilityRealLevelFieldBJ(abil, field, 0, 0.50)
print(5)
end
function DragonAddAttackSpeed(unit, value)
print(1)
local abil = BlzGetUnitAbility(unit, FourCC("A01S:Aegr"))
print(2)
-- ABILITY_RLF_MOVEMENT_SPEED_REDUCTION_PERCENT_NSO4
local field = ConvertAbilityRealLevelField( FourCC('Def3') )
print(3, BlzGetAbilityRealLevelField(abil, field))
BlzSetAbilityRealLevelFieldBJ(abil, field, 0.66)
print(4, BlzGetAbilityRealLevelField(abil, field))
--BlzSetAbilityRealLevelFieldBJ(abil, field, 0, 0.50)
print(5)
end
TextRanksTexts = {}
TextRanksTexts = nil
maxRank = 35
do
TextRanksTexts =
{"F5", "F4", "F3", "F2", "F1", "E5", "E4", "E3", "E2", "E1", "D5", "D4",
"D3", "D2", "D1", "C5", "C4", "C3", "C2", "C1", "B5", "B4", "B3", "B2", "B1", "A5",
"A4", "A3", "A2", "A1", "S5", "S4", "S3", "S2", "S1", }
local t1 = "|cff61D9FF"
local t2 = "|cff71FF6F"
local t3 = "|cffFFED4D"
local t4 = "|cffFF5C00"
local t5 = "|cffFF0F00"
local t6 = "|cffFF00E5"
local t7 = "|cff576AAF"
local tt = function(t) return t, t, t, t, t end
TextRankColors = {t1, t1, t1, t1, t1, t2, t2, t2, t2, t2, t3, t3, t3, t3, t3, t4, t4, t4, t4, t4, t5, t5, t5, t5, t5, t6, t6, t6, t6, t6, t7, t7, t7, t7, t7}
end
function Leadership2Rank(leadership)
for n = 1, #TextRanksTexts do
if TextRanksTo[n] >= leadership then
return n
end
end
return #TextRanksTexts
end
function RankToPower( rank )
return 100.0 * (1.2 ^ (rank - 1))
end
function PowerToRank( power, ceil)
local v = math.min( maxRank, math.max(1, 1 + math.log( power / 100.0 , 1.2)))
if ceil == true then return math.floor(v) end
return v
end
function Pool( arr )
local warr = {}
local totalW = 0
local elements = 0
for i, v in ipairs(arr) do
local value = v[1]
local w = v[2]
if w ~= 0 then
elements = elements + 1
totalW = totalW + w
warr[elements] = {value, w}
end
end
--for i, v in ipairs(warr) do
--warr[i][2] = warr[i][2] / totalW
--end
return {totalW, warr}
end
function PoolGet( pool )
local totalW = pool[1]
local arr = pool[2]
local ch = math.random() * totalW
for i, v in ipairs(arr) do
ch = ch - v[2]
if ch <= 0 then return v[1] end
end
return arr[#arr][1]
end
function Normalization(value, arrSize, dif)
local arr = {}
local total = 0
for i = 1, arrSize do
local v = 1 + math.random() * dif
total = total + v
table.insert(arr, v)
end
for i = 1, arrSize do
arr[i] = value * arr[i] / total
end
return arr
end
ObNames = {}
do
table.insert( ObNames, "Morbid Manor" )
table.insert( ObNames, "Goblin’s Gaff" )
table.insert( ObNames, "Clearwater Cabin" )
table.insert( ObNames, "Portside Place" )
table.insert( ObNames, "Willowed Way" )
table.insert( ObNames, "Lair of the Listless" )
table.insert( ObNames, "Candlewick Manor" )
table.insert( ObNames, "Elfsong Cottage" )
table.insert( ObNames, "Smuggler’s Retreat" )
table.insert( ObNames, "Sandy Shores" )
table.insert( ObNames, "Starboard Manor" )
table.insert( ObNames, "Desecrated Grounds" )
table.insert( ObNames, "Leafy Lodge" )
table.insert( ObNames, "The Werehouse" )
table.insert( ObNames, "Watchtower of the Sailor's Wife" )
table.insert( ObNames, "The Flown Coop" )
table.insert( ObNames, "Ivy Cottage" )
table.insert( ObNames, "The Cauldron" )
table.insert( ObNames, "Crabview Castle" )
table.insert( ObNames, "Bolthole Byre" )
table.insert( ObNames, "Christmas Cottage" )
table.insert( ObNames, "Rippleview Retreat" )
table.insert( ObNames, "Creekview Ridge" )
table.insert( ObNames, "Castle of the Charlatan" )
table.insert( ObNames, "Creekside Cottage" )
table.insert( ObNames, "Lodge in the Lilacs" )
table.insert( ObNames, "Cooper's Coop" )
table.insert( ObNames, "Cursed Cove" )
table.insert( ObNames, "Silent Spires" )
table.insert( ObNames, "Captain's Crib" )
table.insert( ObNames, "Sleepy Cove" )
table.insert( ObNames, "Lakeside Lodge" )
table.insert( ObNames, "The Surf Turf" )
table.insert( ObNames, "Reaper's Roost" )
table.insert( ObNames, "Pirate's Place" )
table.insert( ObNames, "Fisherman's Retreat" )
table.insert( ObNames, "Otter's Holt" )
table.insert( ObNames, "Crocodile Rock" )
table.insert( ObNames, "Wavewatch" )
table.insert( ObNames, "The Empty Halls" )
table.insert( ObNames, "Jouster's Joint" )
table.insert( ObNames, "Sailor's Rest" )
table.insert( ObNames, "Topsy Turvy Towers" )
table.insert( ObNames, "Halls of the Undying" )
table.insert( ObNames, "Mountview Estate" )
table.insert( ObNames, "Hunter's Hideaway" )
table.insert( ObNames, "All Hallows House" )
table.insert( ObNames, "The Lookout" )
table.insert( ObNames, "Ghasthold" )
table.insert( ObNames, "Bolthole Burrow" )
table.insert( ObNames, "Salty Dog Cove" )
table.insert( ObNames, "Twitcher's Tower" )
table.insert( ObNames, "The Unlighthouse" )
table.insert( ObNames, "Goblin's Gaff" )
table.insert( ObNames, "Serenity" )
table.insert( ObNames, "Nook and Cranny Cottage" )
table.insert( ObNames, "Manor of the Maze" )
table.insert( ObNames, "Cabin of Calm" )
table.insert( ObNames, "The Pigpen" )
table.insert( ObNames, "Crabcatcher's Cabin" )
table.insert( ObNames, "The Hull" )
table.insert( ObNames, "Bleeding Lord's Lair" )
table.insert( ObNames, "Lupine Lodge" )
table.insert( ObNames, "Demon's Domain" )
table.insert( ObNames, "Mousehold Heath" )
table.insert( ObNames, "Baywatch" )
table.insert( ObNames, "Fireside Fun" )
table.insert( ObNames, "Halfling House" )
table.insert( ObNames, "Deepest Depths" )
table.insert( ObNames, "Nessie Watch Cottage" )
table.insert( ObNames, "Land's End" )
table.insert( ObNames, "Crooked Nook" )
table.insert( ObNames, "Ghosthome" )
table.insert( ObNames, "Toppled Towers" )
table.insert( ObNames, "Hole in the Wall" )
table.insert( ObNames, "The Formicary" )
table.insert( ObNames, "Nowhere Manor" )
table.insert( ObNames, "Serpentine Estate" )
table.insert( ObNames, "Piney Point" )
table.insert( ObNames, "Driftwood Dwelling" )
table.insert( ObNames, "Saltwater Shack" )
table.insert( ObNames, "Rustic Refuge" )
table.insert( ObNames, "Hero's Hideaway" )
table.insert( ObNames, "Dwarfhome" )
table.insert( ObNames, "Life on the Edge" )
table.insert( ObNames, "Tangled Tower" )
table.insert( ObNames, "The Vespiary" )
table.insert( ObNames, "Hallowed Hideaway" )
table.insert( ObNames, "The Book Nook" )
table.insert( ObNames, "The Devil's Depths" )
table.insert( ObNames, "Sailor's Lookout" )
table.insert( ObNames, "First Mate's Estate" )
table.insert( ObNames, "The Cattery" )
table.insert( ObNames, "Place of Rest" )
table.insert( ObNames, "Resistance Ridge" )
table.insert( ObNames, "Lakeview Lodge" )
table.insert( ObNames, "Springview Cottage" )
table.insert( ObNames, "Restful Ridge" )
table.insert( ObNames, "Angler's Rest" )
table.insert( ObNames, "Reaver's Ridge" )
table.insert( ObNames, "Forgotten Cottage" )
table.insert( ObNames, "Final Resting Place" )
table.insert( ObNames, "Sunsoak Shack" )
table.insert( ObNames, "Gentle Glades" )
table.insert( ObNames, "Grizzly Bear Point" )
table.insert( ObNames, "Shingled Paths" )
table.insert( ObNames, "Ranger's Roost" )
table.insert( ObNames, "Captain's Rest" )
table.insert( ObNames, "Weeping Willow Way" )
table.insert( ObNames, "Oceanview" )
table.insert( ObNames, "Grimehold" )
table.insert( ObNames, "The Empty Nest" )
table.insert( ObNames, "Estate of the Elves" )
table.insert( ObNames, "Edge of the World" )
table.insert( ObNames, "Woodcutter's Lodge" )
table.insert( ObNames, "Salty Breeze" )
table.insert( ObNames, "Heron's Hearth" )
table.insert( ObNames, "Faerie's Rest" )
table.insert( ObNames, "Revolutionary's Refuge" )
table.insert( ObNames, "Demon's Dwelling" )
table.insert( ObNames, "Whisphome" )
table.insert( ObNames, "Greengables" )
table.insert( ObNames, "Honeysuckle Cottage" )
table.insert( ObNames, "Smuggler's Retreat" )
table.insert( ObNames, "Freshwater Haven" )
table.insert( ObNames, "Shifting Cabin" )
table.insert( ObNames, "Rebel's Refuge" )
table.insert( ObNames, "The Endless Estate" )
end
local DataUnit = {}
DataUnit.__index = DataUnit
function DataUnit.New( unitType, level, name )
local self = setmetatable({}, DataUnit)
self.unitType = unitType
self.name = name
self.level = level
return self
end
function DataUnit.GetPower( self )
local lvl = self.level
if lvl > 10 then lvl = 10 end
if lvl < 1 then lvl = 1 end
return UnitLevelsPower()[self.level]
end
function DataUnit.CreateUnit(self, player, x, y, angle )
CreateUnit( player, self.unitType, x, y, angle )
end
local DataUnits = {}
DataUnits.__index = DataUnits
_allDataUnits = nil
function DataUnits.New( )
local self = setmetatable({}, DataUnits)
self.units = {}
self.unitsByType = {}
self.forLevels = {}
for i = 0, 20 do
self.forLevels[i] = {}
end
return self
end
function DataUnits.GetUnitForLevel(self, level )
local table = self.forLevels[level]
return table[ GetRandomInt(1, #table) ]
end
function DataUnits.TestPrint(self, level )
print("unites per level " .. level)
local table = self.forLevels[level]
print("count = " .. #table)
--for i = 1, #table do
-- table[i]:TestPrint()
--end
end
function DataUnits.Add(self, unitType, level, name, inRandomGroups )
if inRandomGroups == nil then inRandomGroups = true end
local dataUnit = DataUnit.New(unitType, level, name )
table.insert( self.units, dataUnit )
--self.units[#self.units + 1] = dataUnit
self.unitsByType[unitType] = dataUnit
if inRandomGroups == true then
local table = self.forLevels[level]
table[#table + 1] = dataUnit
--table.insert( table, dataUnit )
end
--table[#table + 1] = dataUnit
end
function DataUnits.GetUnit(self, unitType)
return self.unitsByType[unitType]
end
function DataUnits.Add2(self, level, dataUnit )
self.units[#self.units + 1] = dataUnit
local table = self.forLevels[level]
table[#table + 1] = dataUnit
end
function AllDataUnits()
if _allDataUnits == nil then _allDataUnits = DataUnits.New() end
return _allDataUnits
end
function DataUnitsFill()
local data = AllDataUnits()
data:Add(FourCC("ugho"), 2, "Ghoul", false)
data:Add(FourCC("uabo"), 4, "Abomination", false)
data:Add(FourCC("unec"), 2, "Necromancer", false)
data:Add(FourCC("nanc"), 1, "Crystal Arachnathid")
data:Add(FourCC("nanm"), 1, "Barbed Arachnathid")
data:Add(FourCC("nanb"), 1, "Barbed Arachnathid")
data:Add(FourCC("nban"), 1, "Bandit")
data:Add(FourCC("nscb"), 1, "Spider Crab Shorecrawler")
data:Add(FourCC("ndrf"), 1, "Draenei Guardian")
data:Add(FourCC("nenc"), 1, "Corrupted Treant")
data:Add(FourCC("nspg"), 1, "Forest Spider")
data:Add(FourCC("nspr"), 1, "Spider")
data:Add(FourCC("nspb"), 1, "Black Spider")
data:Add(FourCC("ngna"), 1, "Gnoll Poacher")
data:Add(FourCC("ngno"), 1, "Gnoll")
data:Add(FourCC("nhar"), 1, "Harpy Scout")
data:Add(FourCC("nhfp"), 1, "Fallen Priest")
data:Add(FourCC("nkob"), 1, "Kobold")
data:Add(FourCC("nlpr"), 1, "Makrura Prawn")
data:Add(FourCC("nwiz"), 1, "Apprentice Wizard")
data:Add(FourCC("nmcf"), 1, "Mur'gul Cliffrunner")
data:Add(FourCC("nmrl"), 1, "Murloc Tiderunner")
data:Add(FourCC("nspd"), 1, "Spiderling")
data:Add(FourCC("nrzs"), 1, "Razormane Scout")
data:Add(FourCC("nrzt"), 1, "Quillboar")
data:Add(FourCC("nsty"), 1, "Satyr")
data:Add(FourCC("nsat"), 1, "Satyr Trickster")
data:Add(FourCC("nslm"), 1, "Sludge Minion")
data:Add(FourCC("nsra"), 1, "Stormreaver Apprentice")
data:Add(FourCC("ntrh"), 1, "Sea Turtle Hatchling")
data:Add(FourCC("nvdl"), 1, "Lesser Voidwalker")
data:Add(FourCC("nska"), 1, "Skeleton Archer")
data:Add(FourCC("ndrj"), 1, "Nodal experiment")
data:Add(FourCC("nbrg"), 2, "Brigand")
data:Add(FourCC("ncer"), 2, "Centaur Drudge")
data:Add(FourCC("ncea"), 2, "Centaur Archer")
data:Add(FourCC("ndtr"), 2, "Dark Troll")
data:Add(FourCC("ndtp"), 2, "Dark Troll Shadow Priest")
data:Add(FourCC("ndrp"), 2, "Draenei Protector")
data:Add(FourCC("ndrm"), 2, "Draenei Disciple")
data:Add(FourCC("nrel"), 2, "Reef Elemental")
data:Add(FourCC("nfgu"), 2, "Felguard")
data:Add(FourCC("nfsp"), 2, "Forest Troll Shadow Priest")
data:Add(FourCC("nftr"), 2, "Forest Troll")
data:Add(FourCC("ngrk"), 2, "Mud Golem")
data:Add(FourCC("nitr"), 2, "Ice Troll")
data:Add(FourCC("nitp"), 2, "Ice Troll Priest")
data:Add(FourCC("nltl"), 2, "Lightning Lizard")
data:Add(FourCC("nltc"), 2, "Makrura Tidecaller")
data:Add(FourCC("nlpd"), 2, "Makrura Pooldweller")
data:Add(FourCC("nmbg"), 2, "Mur'gul Blood-Gill")
data:Add(FourCC("nmrr"), 2, "Murloc Huntsman")
data:Add(FourCC("nmpg"), 2, "Murloc Plaguebearer")
data:Add(FourCC("ntrs"), 2, "Sea Turtle")
data:Add(FourCC("ntka"), 2, "Tuskarr Spearman")
data:Add(FourCC("ntkf"), 2, "Tuskarr Fighter")
data:Add(FourCC("nubk"), 2, "Unbroken Darkhunter")
data:Add(FourCC("nwlt"), 2, "Timber Wolf")
data:Add(FourCC("nwwf"), 2, "Frost Wolf")
data:Add(FourCC("ndmu"), 2, "Dalaran mutant")
data:Add(FourCC("nanw"), 3, "Warrior Arachnathid")
data:Add(FourCC("nrog"), 3, "Rogue")
data:Add(FourCC("nbdm"), 3, "Blue Dragonspawn Meddler")
data:Add(FourCC("nsc2"), 3, "Spider Crab Limbripper")
data:Add(FourCC("ndtt"), 3, "Dark Troll Trapper")
data:Add(FourCC("ndrw"), 3, "Draenei Watcher")
data:Add(FourCC("nrdk"), 3, "Red Dragon Whelp")
data:Add(FourCC("nbdr"), 3, "Black Dragon Whelp")
data:Add(FourCC("nbzw"), 3, "Bronze Dragon Whelp")
data:Add(FourCC("ngrw"), 3, "Green Dragon Whelp")
data:Add(FourCC("nadw"), 3, "Blue Dragon Whelp")
data:Add(FourCC("nnht"), 3, "Nether Dragon Hatchling")
data:Add(FourCC("nenp"), 3, "Poison Treant")
data:Add(FourCC("npfl"), 3, "Fel Beast")
data:Add(FourCC("nftt"), 3, "Forest Troll Trapper")
data:Add(FourCC("ngh1"), 3, "Ghost")
data:Add(FourCC("nsgn"), 3, "Sea Giant")
data:Add(FourCC("nssp"), 3, "Spitting Spider")
data:Add(FourCC("ngns"), 3, "Gnoll Assassin")
data:Add(FourCC("ngnb"), 3, "Gnoll Brute")
data:Add(FourCC("ngnw"), 3, "Gnoll Warden")
data:Add(FourCC("narg"), 3, "Battle Golem")
data:Add(FourCC("nhrw"), 3, "Harpy Windwitch")
data:Add(FourCC("nhrr"), 3, "Harpy Rogue")
data:Add(FourCC("nhdc"), 3, "Deceiver")
data:Add(FourCC("nhyh"), 3, "Hydra Hatchling")
data:Add(FourCC("nitt"), 3, "Ice Troll Trapper")
data:Add(FourCC("nkog"), 3, "Kobold Geomancer")
data:Add(FourCC("nkot"), 3, "Kobold Tunneler")
data:Add(FourCC("nwzr"), 3, "Rogue Wizard")
data:Add(FourCC("nmam"), 3, "Mammoth")
data:Add(FourCC("nmtw"), 3, "Mur'gul Tidewarrior")
data:Add(FourCC("nmrm"), 3, "Murloc Nightcrawler")
data:Add(FourCC("nmfs"), 3, "Murloc Flesheater")
data:Add(FourCC("nnwa"), 3, "Nerubian Warrior")
data:Add(FourCC("nnwl"), 3, "Nerubian Webspinner")
data:Add(FourCC("nogr"), 3, "Ogre Warrior")
data:Add(FourCC("nrzb"), 3, "Razormane Brute")
data:Add(FourCC("nqbh"), 3, "Quillboar Hunter")
data:Add(FourCC("ntrv"), 3, "Revenant of the Tides")
data:Add(FourCC("nrvf"), 3, "Fire Revenant")
data:Add(FourCC("nslh"), 3, "Salamander Hatchling")
data:Add(FourCC("nsts"), 3, "Satyr Shadowdancer")
data:Add(FourCC("nsko"), 3, "Skeletal Orc")
data:Add(FourCC("nslf"), 3, "Sludge Flinger")
data:Add(FourCC("nsrh"), 3, "Stormreaver Hermit")
data:Add(FourCC("ndqn"), 3, "Succubus")
data:Add(FourCC("ntkh"), 3, "Tuskarr Healer")
data:Add(FourCC("nvdw"), 3, "Voidwalker")
data:Add(FourCC("nskf"), 3, "Burning Archer")
data:Add(FourCC("nskm"), 3, "Skeletal Marksman")
data:Add(FourCC("njg1"), 3, "Jungle Stalker")
data:Add(FourCC("nskg"), 3, "Giant Skeleton Warrior")
data:Add(FourCC("nane"), 4, "Arachnathid Earth-borer")
data:Add(FourCC("nass"), 4, "Assassin")
data:Add(FourCC("nbda"), 4, "Blue Dragonspawn Apprentice")
data:Add(FourCC("ncen"), 4, "Centaur Outrunner")
data:Add(FourCC("ncim"), 4, "Centaur Impaler")
data:Add(FourCC("ndtb"), 4, "Dark Troll Berserker")
data:Add(FourCC("ndth"), 4, "Dark Troll High Priest")
data:Add(FourCC("ndrh"), 4, "Draenei Harbinger")
data:Add(FourCC("nele"), 4, "Enraged Elemental")
data:Add(FourCC("ners"), 4, "Eredar Sorcerer")
data:Add(FourCC("nfgb"), 4, "Bloodfiend")
data:Add(FourCC("nftb"), 4, "Forest Troll Berserker")
data:Add(FourCC("nfsh"), 4, "Forest Troll High Priest")
data:Add(FourCC("nfrp"), 4, "Primal Pandaren")
data:Add(FourCC("nfrl"), 4, "Furbolg")
data:Add(FourCC("nfrs"), 4, "Furbolg Shaman")
data:Add(FourCC("nsgt"), 4, "Giant Spider")
data:Add(FourCC("nits"), 4, "Ice Troll Berserker")
data:Add(FourCC("nith"), 4, "Ice Troll High Priest")
data:Add(FourCC("nmsn"), 4, "Mur'gul Snarecaster")
data:Add(FourCC("nowb"), 4, "Wildkin")
data:Add(FourCC("nplb"), 4, "Polar Bear")
data:Add(FourCC("nfpl"), 4, "Polar Furbolg")
data:Add(FourCC("nfps"), 4, "Polar Furbolg Shaman")
data:Add(FourCC("nrvs"), 4, "Frost Revenant")
data:Add(FourCC("ntrt"), 4, "Giant Sea Turtle")
data:Add(FourCC("ntkw"), 4, "Tuskarr Warrior")
data:Add(FourCC("ntkt"), 4, "Tuskarr Trapper")
data:Add(FourCC("nubr"), 4, "Unbroken Rager")
data:Add(FourCC("nwen"), 4, "Wendigo")
data:Add(FourCC("nwlg"), 4, "Giant Wolf")
data:Add(FourCC("nwwg"), 4, "Giant Frost Wolf")
data:Add(FourCC("nano"), 5, "Overlord Arachnathid")
data:Add(FourCC("nenf"), 5, "Enforcer")
data:Add(FourCC("nbdw"), 5, "Blue Dragonspawn Warrior")
data:Add(FourCC("ncks"), 5, "Centaur Sorcerer")
data:Add(FourCC("nsc3"), 5, "Spider Crab Behemoth")
data:Add(FourCC("ndrd"), 5, "Draenei Darkslayer")
data:Add(FourCC("nsel"), 5, "Sea Elemental")
data:Add(FourCC("nepl"), 5, "Plague Treant")
data:Add(FourCC("nfel"), 5, "Fel Stalker")
data:Add(FourCC("nsgh"), 5, "Sea Giant Hunter")
data:Add(FourCC("ngnv"), 5, "Gnoll Overseer")
data:Add(FourCC("nhrh"), 5, "Harpy Storm-hag")
data:Add(FourCC("nhhr"), 5, "Heretic")
data:Add(FourCC("ninc"), 5, "Infernal Contraption")
data:Add(FourCC("nkol"), 5, "Kobold Taskmaster")
data:Add(FourCC("nlds"), 5, "Makrura Deepseer")
data:Add(FourCC("nlsn"), 5, "Makrura Snapper")
data:Add(FourCC("nwzg"), 5, "Renegade Wizard")
data:Add(FourCC("nmgw"), 5, "Magnataur Warrior")
data:Add(FourCC("nmit"), 5, "Icetusk Mammoth")
data:Add(FourCC("nnws"), 5, "Nerubian Spider Lord")
data:Add(FourCC("nnwr"), 5, "Nerubian Seer")
data:Add(FourCC("nogm"), 5, "Ogre Mauler")
data:Add(FourCC("nomg"), 5, "Ogre Magi")
data:Add(FourCC("nrzm"), 5, "Razormane Medicine Man")
data:Add(FourCC("nsrv"), 5, "Revenant of the Seas")
data:Add(FourCC("nslr"), 5, "Salamander")
data:Add(FourCC("nsqt"), 5, "Sasquatch")
data:Add(FourCC("nstl"), 5, "Satyr Soulstealer")
data:Add(FourCC("nsog"), 5, "Skeletal Orc Grunt")
data:Add(FourCC("nsln"), 5, "Sludge Monstrosity")
data:Add(FourCC("ndqv"), 5, "Vile Tormentor")
data:Add(FourCC("ntks"), 5, "Tuskarr Sorcerer")
data:Add(FourCC("nubw"), 5, "Unbroken Darkweaver")
data:Add(FourCC("nbds"), 6, "Blue Dragonspawn Sorcerer")
data:Add(FourCC("ndtw"), 6, "Dark Troll Warlord")
data:Add(FourCC("ndrs"), 6, "Draenei Seer")
data:Add(FourCC("nrdr"), 6, "Red Drake")
data:Add(FourCC("nbdk"), 6, "Black Drake")
data:Add(FourCC("nbzk"), 6, "Bronze Drake")
data:Add(FourCC("ngdk"), 6, "Green Drake")
data:Add(FourCC("nadk"), 6, "Blue Drake")
data:Add(FourCC("nndk"), 6, "Nether Drake")
data:Add(FourCC("nerd"), 6, "Eredar Diabolist")
data:Add(FourCC("nfor"), 6, "Faceless One Trickster")
data:Add(FourCC("nfov"), 6, "Overlord")
data:Add(FourCC("nftk"), 6, "Forest Troll Warlord")
data:Add(FourCC("nfrb"), 6, "Furbolg Tracker")
data:Add(FourCC("ngh2"), 6, "Wraith")
data:Add(FourCC("nsbm"), 6, "Brood Mother")
data:Add(FourCC("ngst"), 6, "Rock Golem")
data:Add(FourCC("nwrg"), 6, "War Golem")
data:Add(FourCC("nhyd"), 6, "Hydra")
data:Add(FourCC("nitw"), 6, "Ice Troll Warlord")
data:Add(FourCC("nthl"), 6, "Thunder Lizard")
data:Add(FourCC("nmrv"), 6, "Mur'gul Marauder")
data:Add(FourCC("nmmu"), 6, "Murloc Mutant")
data:Add(FourCC("nowe"), 6, "Enraged Wildkin")
data:Add(FourCC("nplg"), 6, "Giant Polar Bear")
data:Add(FourCC("nfpt"), 6, "Polar Furbolg Tracker")
data:Add(FourCC("nrvl"), 6, "Lightning Revenant")
data:Add(FourCC("nsqe"), 6, "Elder Sasquatch")
data:Add(FourCC("nsrn"), 6, "Stormreaver Necrolyte")
data:Add(FourCC("ndqt"), 6, "Vile Temptress")
data:Add(FourCC("nvdg"), 6, "Greater Voidwalker")
data:Add(FourCC("nwnr"), 6, "Elder Wendigo")
data:Add(FourCC("nwld"), 6, "Dire Wolf")
data:Add(FourCC("nwwd"), 6, "Dire Frost Wolf")
data:Add(FourCC("njga"), 6, "Elder Jungle Stalker")
data:Add(FourCC("nbld"), 7, "Bandit Lord")
data:Add(FourCC("npfm"), 7, "Fel Ravager")
data:Add(FourCC("nfre"), 7, "Furbolg Elder Shaman")
data:Add(FourCC("nfrg"), 7, "Furbolg Champion")
data:Add(FourCC("nhrq"), 7, "Harpy Queen")
data:Add(FourCC("nehy"), 7, "Elder Hydra")
data:Add(FourCC("nlkl"), 7, "Makrura Tidal Lord")
data:Add(FourCC("nmsc"), 7, "Mur'gul Shadowcaster")
data:Add(FourCC("nnwq"), 7, "Nerubian Queen")
data:Add(FourCC("nogl"), 7, "Ogre Lord")
data:Add(FourCC("nfpc"), 7, "Polar Furbolg Champion")
data:Add(FourCC("nrzg"), 7, "Razormane Chieftain")
data:Add(FourCC("nslv"), 7, "Salamander Vizier")
data:Add(FourCC("nsqo"), 7, "Sasquatch Oracle")
data:Add(FourCC("ntrg"), 7, "Gargantuan Sea Turtle")
data:Add(FourCC("ntkc"), 7, "Tuskarr Chieftain")
data:Add(FourCC("nwns"), 7, "Wendigo Shaman")
data:Add(FourCC("nbdo"), 8, "Blue Dragonspawn Overseer")
data:Add(FourCC("ncnk"), 8, "Centaur Khan")
data:Add(FourCC("nelb"), 8, "Berserk Elemental")
data:Add(FourCC("nfot"), 8, "Faceless One Terror")
data:Add(FourCC("nfra"), 8, "Furbolg Ursa Warrior")
data:Add(FourCC("nsgb"), 8, "Sea Giant Behemoth")
data:Add(FourCC("ninm"), 8, "Infernal Machine")
data:Add(FourCC("nwzd"), 8, "Dark Wizard")
data:Add(FourCC("nmgr"), 8, "Magnataur Reaver")
data:Add(FourCC("nmdr"), 8, "Dire Mammoth")
data:Add(FourCC("nowk"), 8, "Berserk Wildkin")
data:Add(FourCC("nfpu"), 8, "Polar Furbolg Ursa Warrior")
data:Add(FourCC("nfpe"), 8, "Polar Furbolg Elder Shaman")
data:Add(FourCC("ndrv"), 8, "Revenant of the Depths")
data:Add(FourCC("nrvi"), 8, "Ice Revenant")
data:Add(FourCC("nsoc"), 8, "Skeletal Orc Champion")
data:Add(FourCC("ndqp"), 8, "Maiden of Pain")
data:Add(FourCC("ninf"), 8, "Infernal")
data:Add(FourCC("nbal"), 8, "Doom Guard")
data:Add(FourCC("nerw"), 9, "Eredar Warlock")
data:Add(FourCC("nggr"), 9, "Granite Golem")
data:Add(FourCC("nsgg"), 9, "Siege Golem")
data:Add(FourCC("nstw"), 9, "Storm Wyrm")
data:Add(FourCC("nrvd"), 9, "Death Revenant")
data:Add(FourCC("nsqa"), 9, "Ancient Sasquatch")
data:Add(FourCC("nsth"), 9, "Satyr Hellcaller")
data:Add(FourCC("nsrw"), 9, "Stormreaver Warlock")
data:Add(FourCC("nvde"), 9, "Elder Voidwalker")
data:Add(FourCC("nwna"), 9, "Ancient Wendigo")
data:Add(FourCC("njgb"), 9, "Enraged Jungle Stalker")
data:Add(FourCC("nrwm"), 10, "Red Dragon")
data:Add(FourCC("nbwm"), 10, "Black Dragon")
data:Add(FourCC("nbzd"), 10, "Bronze Dragon")
data:Add(FourCC("ngrd"), 10, "Green Dragon")
data:Add(FourCC("nadr"), 10, "Blue Dragon")
data:Add(FourCC("nndr"), 10, "Nether Dragon")
data:Add(FourCC("nfod"), 10, "Faceless One Deathbringer")
data:Add(FourCC("nahy"), 10, "Ancient Hydra")
data:Add(FourCC("nina"), 10, "Infernal Juggernaut")
data:Add(FourCC("nmgd"), 10, "Magnataur Destroyer")
data:Add(FourCC("nlrv"), 10, "Deeplord Revenant")
data:Add(FourCC("nsll"), 10, "Salamander Lord")
data:Add(FourCC("ndqs"), 10, "Queen of Suffering")
data:Add(FourCC("ntrd"), 10, "Dragon Turtle")
end
DataLocationsByIndex = {}
DataLocationsById = {}
function InitDataLocations()
local newData = function(unType, id, name)
loc = {}
loc.unType = unType
loc.name = name
DataLocationsByIndex[ #DataLocationsByIndex + 1 ] = loc
DataLocationsById[ id ] = loc
end
newData( FourCC("n00H"), "cartography", "Cartography")
newData( FourCC("n00L"), "champions", "Altar of Champions")
newData( FourCC("n00J"), "guildHeroes", "Guild of Heroes")
newData( FourCC("n00K"), "mercenary", "Mercenary Camp")
newData( FourCC("n00E"), "newcomer", "Newcomer Camp")
newData( FourCC("n00D"), "pandora", "Pandora's Box")
newData( FourCC("n00C"), "tres_1", "Small Treasure")
newData( FourCC("n00G"), "tavern", "Tavern")
newData( FourCC("n00F"), "knowledge", "The Altar of Knowledge")
newData( FourCC("n00I"), "demons", "Demonic Portal")
newData( FourCC("n00M"), "forgotten", "Forgotten")
end
_unitLevelsPower = nil
function UnitLevelsPower()
if _unitLevelsPower == nil then
_unitLevelsPower = {50, 80, 110, 160, 220, 290, 340, 480, 700, 1000}
end
return _unitLevelsPower
end
function MinLevelForPower(power, maxCount)
for i = 1, 9 do
if UnitLevelsPower()[i] * maxCount >= power then return i end
end
return 10
end
function MaxLevelForPower(power, minCount)
for i = 2, 10 do
if UnitLevelsPower()[i] * minCount >= power then return i-1 end
end
return 10
end
function RandLevelForPower(power, minCount, maxCount)
return math.random( MinLevelForPower(power, maxCount), MaxLevelForPower(power, minCount) )
end
function ArmyForPower(slots, leadFrom, leadTo)
local npower = {50, 80, 110, 160, 220, 290, 340, 480, 700, 1000}
local stacksMult = {36, 18, 12, 9}
local maxpowers = {600, 960, 1320, 1920, 2640, 3480, 4080, 5760, 8400, 12000}
local MinLevel = function(power, stacksCount)
for i = 1, 9 do
if npower[i] * stacksMult[stacksCount] >= power then return i end
end
return 10
end
local MaxLevel = function(power)
for i = 2, 10 do
if npower[i] > power then return i-1 end
end
return 10
end
local lead = 0
if leadTo == nil then
lead = leadFrom
else
lead = GetRandomReal(leadFrom, leadTo)
end
local wgCount = 0
if lead >= 5500 then
wgCount = GetRandomInt(2, 3)
elseif lead >= 950 then
wgCount = GetRandomInt(2, 3)
elseif lead >= 420 then
wgCount = GetRandomInt(1, 3)
elseif lead >= 200 then
wgCount = GetRandomInt(1, 2)
else
wgCount = 1
end
wgCount = slots
local weighs = nil
local totalWeights = 0
if wgCount == 1 then
weighs = {lead}
end
if wgCount == 2 then
weighs = {GetRandomReal(0.4, 1), GetRandomReal(0.4, 1)}
totalWeights = weighs[1] + weighs[2]
for i = 1, wgCount do
weighs[i] = lead * weighs[i] / totalWeights
end
end
if wgCount == 3 then
weighs = {GetRandomReal(0.4, 1), GetRandomReal(0.4, 1), GetRandomReal(0.4, 1)}
totalWeights = weighs[1] + weighs[2] + weighs[3]
for i = 1, wgCount do
weighs[i] = lead * weighs[i] / totalWeights
end
end
if wgCount == 4 then
weighs = {GetRandomReal(0.4, 1), GetRandomReal(0.4, 1), GetRandomReal(0.4, 1), GetRandomReal(0.4, 1)}
totalWeights = weighs[1] + weighs[2] + weighs[3]+ weighs[4]
for i = 1, wgCount do
weighs[i] = lead * weighs[i] / totalWeights
end
end
local monsters = {}
local army = {monsters=monsters, power=0, rank=0}
for i = 1, wgCount do
local level = GetRandomInt( MinLevel(weighs[i], wgCount), MaxLevel(weighs[i]) )
local count = math.floor( weighs[i] / npower[level] + 0.5)
if count < 1 then count = 1 end
local dataUnit = AllDataUnits():GetUnitForLevel( level )
local uType = dataUnit.unitType
monsters[i] = {}
monsters[i].level = level
monsters[i].count = count
monsters[i].unitDB = dataUnit
--monsters[i].unitType = uType
--army:Add( uType, count, level )
army.power = army.power + count * npower[level]
end
army.rank = PowerToRank(army.power, true)
return army
end
function DataInit()
DataUnitsFill()
end
local Story = {}
ActualStory = nil
Story.__index = Story
_stories = {}
function Story.New( id, name )
local self = setmetatable({}, Story)
self.name = name
_stories[ id ] = self
self.isOldBalance = false
self.depth = 16
self.depthInfo = {}
local fP = function(v0, v1, v2) return Pool({{0, v0}, {1, v1}, {2, v2}}) end
self.depthInfo[0] = {rankFrom=1, rankTo=1, slots=1, nextDepths=fP(0, 1, 0)}
self.depthInfo[1] = {rankFrom=5, rankTo=6, slots=1, nextDepths=fP(33, 33, 33)}
self.depthInfo[2] = {rankFrom=7, rankTo=8, slots=2, nextDepths=fP(33, 33, 33)}
self.depthInfo[3] = {rankFrom=9, rankTo=10, slots=2, nextDepths=fP(33, 33, 33)}
self.depthInfo[4] = {rankFrom=11, rankTo=12, slots=3, nextDepths=fP(35, 35, 30)}
self.depthInfo[5] = {rankFrom=13, rankTo=14, slots=3, nextDepths=fP(40, 30, 30)}
self.depthInfo[6] = {rankFrom=15, rankTo=16, slots=3, nextDepths=fP(45, 30, 25)}
self.depthInfo[7] = {rankFrom=17, rankTo=18, slots=3, nextDepths=fP(50, 30, 20)}
self.depthInfo[8] = {rankFrom=19, rankTo=20, slots=3, nextDepths=fP(55, 25, 20)}
self.depthInfo[9] = {rankFrom=21, rankTo=22, slots=3, nextDepths=fP(55, 30, 15)}
self.depthInfo[10] = {rankFrom=23, rankTo=23, slots=3, nextDepths=fP(60, 25, 15)}
self.depthInfo[11] = {rankFrom=24, rankTo=24, slots=3, nextDepths=fP(65, 25, 10)}
self.depthInfo[12] = {rankFrom=25, rankTo=25, slots=3, nextDepths=fP(70, 20, 10)}
self.depthInfo[13] = {rankFrom=26, rankTo=26, slots=3, nextDepths=fP(75, 15, 10)}
self.depthInfo[14] = {rankFrom=27, rankTo=27, slots=3, nextDepths=fP(80, 12, 8)}
self.depthInfo[15] = {rankFrom=28, rankTo=28, slots=3, nextDepths=fP(90, 10, 0)}
self.depthInfo[16] = {rankFrom=29, rankTo=30, slots=3, nextDepths=fP(1, 0, 0)}
self.goldRank1 = 20
self.goldMultPerRank = 1.15
self.xpRank1 = 40
self.xpMultPerRank = 1.19
return self
end
function Story.NextDepth(self, curDepth )
local d = curDepth + PoolGet( self.depthInfo[curDepth].nextDepths )
if d < 1 then d = 1 end
if d > self.depth then d = self.depth end
return d
end
function Story.IdentifyMonsters(self, depth)
local monsters = {}
local template = self.depthInfo[depth]
if depth < 1 then return {} end
local power = math.random(RankToPower(template.rankFrom), RankToPower(template.rankTo))
power = power * Dif().power
local army = ArmyForPower( template.slots, power)
return army.monsters
end
function Story.SetBattleRegion(self, battleRegion)
self.battleRegion = battleRegion
self.inBattle = (battleRegion ~= nil)
end
function Story.SetPoi(self, poi)
self.poi = poi
end
do
local real = createStory1
function createStory1()
real()
--local storyTest = Story.New( "test1", "Test Story" )
--storyTest.isOldBalance = true
--local story1 = Story.New( "story1", "Infernal Forge" )
--ActualStory = story1
end
end
function testStory()
local storyTest = Story.New( "test1", "Test Story" )
storyTest.isOldBalance = true
local story1 = Story.New( "story1", "Infernal Forge" )
ActualStory = story1
end
_storyDescrs = nil
_storyHeaders = nil
storyButtonData = {}
uiStoryPanel = {}
selectedDif = 2
_difParams = nil
discord = nil
inSelectedStory = false -- process selected story
function Dif()
return DifParams()[selectedDif]
end
function DifParams()
if _difParams == nil then
_difParams = {}
for i = 1, 5 do
_difParams[i] = {}
end
_difParams[1].power = 0.8
_difParams[1].stats = 1
_difParams[1].goldReward = 1.2
_difParams[1].xpReward = 1
_difParams[1].addMove = 6
_difParams[1].soulsUpgrade = 1
_difParams[1].wood = 0.5
_difParams[2].power = 1
_difParams[2].stats = 1
_difParams[2].goldReward = 1
_difParams[2].xpReward = 1
_difParams[2].addMove = 4
_difParams[2].soulsUpgrade = 1
_difParams[2].wood = 1
_difParams[3].power = 1.2
_difParams[3].stats = 1.15
_difParams[3].goldReward = 0.9
_difParams[3].xpReward = 0.8
_difParams[3].addMove = 2
_difParams[3].soulsUpgrade = 0.5
_difParams[3].wood = 1.5
_difParams[4].power = 1.25
_difParams[4].stats = 1.3
_difParams[4].goldReward = 0.8
_difParams[4].xpReward = 0.6
_difParams[4].addMove = 0
_difParams[4].soulsUpgrade = 0.25
_difParams[4].wood = 2.2
_difParams[5].power = 1.3
_difParams[5].stats = 1.4
_difParams[5].goldReward = 0.75
_difParams[5].xpReward = 0.45
_difParams[5].addMove = 0
_difParams[5].soulsUpgrade = 0.1
_difParams[5].wood = 3
_difParams[1].descr = [[Number and level of enemy troops |cff7dff7d-20%%|r
Bonus to enemy characteristics |cffffff00+0%%|r
Gold for clearing locations |cff7dff7d+20%%|r
Experience for clearing locations |cffffff00+0%%|r
Bonus movement points |cff7dff7d+6|r
Efficiency of soul upgrades |cffffff00+0%%|r
Wood for clearing locations |cffff8080-50%%|r]]
_difParams[2].descr = [[Number and level of enemy troops |cffffff00+0%%|r
Bonus to enemy characteristics |cffffff00+0%%|r
Gold for clearing locations |cffffff00+0%%|r
Experience for clearing locations |cffffff00+0%%|r
Bonus movement points |cff7dff7d+4|r
Efficiency of soul upgrades |cffffff00+0%%|r
Wood for clearing locations |cffffff00+0%%|r]]
_difParams[3].descr = [[Number and level of enemy troops |cffff8080+20%%|r
Bonus to enemy characteristics |cffff8080+15%%|r
Gold for clearing locations |cffff8080-10%%|r
Experience for clearing locations |cffff8080-20%%|r
Bonus movement points |cff7dff7d+2|r
Efficiency of soul upgrades |cffff8080-50%%|r
Wood for clearing locations |cff7dff7d+50%%|r]]
_difParams[4].descr = [[Number and level of enemy troops |cffff8080+25%%|r
Bonus to enemy characteristics |cffff8080+30%%|r
Gold for clearing locations |cffff8080-20%%|r
Experience for clearing locations |cffff8080-40%%|r
Bonus movement points |cffffff00+0|r
Efficiency of soul upgrades |cffff8080-75%%|r
Wood for clearing locations |cff7dff7d+120%%|r]]
_difParams[5].descr = [[Number and level of enemy troops |cffff8080+30%%|r
Bonus to enemy characteristics |cffff8080+40%%|r
Gold for clearing locations |cffff8080-25%%|r
Experience for clearing locations |cffff8080-55%%|r
Bonus movement points |cffffff00+0|r
Efficiency of soul upgrades |cffff8080-90%%|r
Wood for clearing locations |cff7dff7d+200%%|r]]
end
return _difParams
end
function inirStoryDescrs()
_storyDescrs = {}
_storyHeaders = {}
-- story 1
_storyHeaders.story1 = "|cffffcc00Story I. Infernal Forge|r"
_storyHeaders.story2 = "|cffffcc00Story II. ???|r"
_storyHeaders.story3 = "|cffffcc00Story III. ???|r"
_storyHeaders.story4 = "|cffffcc00Story IV. ???|r"
_storyHeaders.story5 = "|cffffcc00Story V. ???|r"
_storyHeaders.story6 = "|cffffcc00Story VI. ???|r"
_storyHeaders.story7 = "|cffffcc00Story VII. ???|r"
_storyDescrs.story1 =
[[|cffffcc00Final goal:|r find and destroy the |cffff8080Infernal Forge.|r
|cffffcc00Hurry!|r You have |cffffcc0014|r transitions left.
Some time ago, renegade human alchemists and orc demonologists united to achieve common ungodly goals.
The combined knowledge of this unnatural union has given birth to terrifying monsters and, more importantly, the Forge, with which they hope to create weapons of terrifying power.
We must raze the Forge to the ground before the renegades create a weapon that can destroy the world.]]
-- story 2
_storyDescrs.story2 =
[[Story 2 in development]]
-- story 3
_storyDescrs.story3 =
[[Story 3 in development]]
-- story 4
_storyDescrs.story4 =
[[Story 4 in development]]
-- story 5
_storyDescrs.story5 =
[[Story 5 in development]]
-- story 6
_storyDescrs.story6 =
[[Story 6 in development]]
end
function StoryDescrs()
if _storyDescrs == nil then inirStoryDescrs() end
return _storyDescrs
end
function StoryHeaders()
if _storyDescrs == nil then inirStoryDescrs() end
return _storyHeaders
end
function CreateButtonGlory(txt, bIndex)
local parent = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
local button = BlzCreateFrameByType("GLUETEXTBUTTON", "", parent, "ScriptDialogButton", 0)
BlzFrameSetLevel(button, 2)
BlzFrameSetText(button, txt)
BlzFrameSetAbsPoint(button, FRAMEPOINT_CENTER, 0.24, 0.51 - 0.032 * bIndex)
return button
end
chapterUI = {}
function CreateChapterInfo()
--uiStoryPanel = {}
local frame = nil
frame = BlzCreateFrame("QuestButtonBaseTemplate", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0, 0)
uiStoryPanel.mainFraim = frame
BlzFrameSetAbsPoint(frame, FRAMEPOINT_TOPLEFT, 0.35, 0.54)
BlzFrameSetSize(frame, 0.42, 0.39)
--local discord = BlzCreateFrameByType("BUTTON", "MyIconButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "ScoreScreenTabButtonTemplate", 0)
if false then
discord = BlzCreateFrameByType("BACKDROP", "MyIconButtonIcon", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
BlzFrameSetAbsPoint(discord, FRAMEPOINT_BOTTOMLEFT, 0.01, 0.01)
BlzFrameSetSize(discord, 0.15, 0.15)
BlzFrameSetTexture(discord, "Discrod.tga", 0, false)
end
--uiStoryPanel.discord = discord
local header = BlzCreateFrameByType("TEXT", "MyTextFrame", frame, "", 0)
BlzFrameSetText(header, "|cffffcc00Story I. Infernal Forge|r")
BlzFrameSetAbsPoint(header, FRAMEPOINT_TOPLEFT, 0.36, 0.53)
BlzFrameSetEnable(header, false)
BlzFrameSetScale(header, 2)
uiStoryPanel.header = header
local descr = BlzCreateFrameByType("TEXTAREA", "", frame, "EscMenuTextAreaTemplate", 0)
uiStoryPanel.descr = descr
BlzFrameSetPoint(descr, FRAMEPOINT_BOTTOMRIGHT, frame, FRAMEPOINT_BOTTOMRIGHT, 0, 0)
BlzFrameSetPoint(descr, FRAMEPOINT_TOPLEFT, frame, FRAMEPOINT_TOPLEFT, 0.0, -0.024)
--local descr = BlzCreateFrameByType("TEXT", "MyTextFrame", frame, "", 0)
BlzFrameSetText(descr, StoryDescrs().story1)
--BlzFrameSetAbsPoint(descr, FRAMEPOINT_TOPLEFT, 0.36, 0.51)
BlzFrameSetEnable(descr, false)
BlzFrameSetScale(descr, 1)
local triggerDifClick = CreateTrigger()
TriggerAddAction(triggerDifClick, function()
local frame = BlzGetTriggerFrame()
local names = {"easy", "normal", "hard", "veryhard", "hell"}
for i = 1, 5 do
if frame == uiStoryPanel.difs[i] then
if GetTriggerPlayer() == Player(0) then
QuestMessageBJ(GetPlayersAll(), bj_QUESTMESSAGE_UPDATED, GetPlayerName(GetTriggerPlayer()) .. " select difficulty " .. i)
selectedDif = i
local path = "war3mapImported\\" .. names[i] .. "_s.tga"
if uiStoryPanel.difsIcon[i] == nil then print("NIL!") end
BlzFrameSetTexture(uiStoryPanel.difsIcon[i], path, 0, false)
else
QuestMessageBJ(GetPlayersAll(), bj_QUESTMESSAGE_UPDATED, GetPlayerName(GetTriggerPlayer()) .. " wants difficulty number " .. i)
end
else
if GetTriggerPlayer() == Player(0) then
local path = "war3mapImported\\" .. names[i] .. ".tga"
if uiStoryPanel.difsIcon[i] == nil then print("NIL!") end
BlzFrameSetTexture(uiStoryPanel.difsIcon[i], path, 0, false)
end
end
end
UpdatePanelDif()
end)
uiStoryPanel.difs = {}
uiStoryPanel.difsIcon = {}
local positionsX = {-0.054*2, -0.054, 0, 0.054, 0.054*2}
for i = 1, 5 do
local b, icon = createDiffIcon(frame, i, i==selectedDif)
BlzTriggerRegisterFrameEvent(triggerDifClick, b, FRAMEEVENT_CONTROL_CLICK)
uiStoryPanel.difs[i], uiStoryPanel.difsIcon[i] = b, icon
--BlzFrameSetAbsPoint(b, FRAMEPOINT_BOTTOMLEFT, 0.36 + 0.054 * (i-1), 0.2 + 0.031)
BlzFrameSetPoint(b, FRAMEPOINT_BOTTOM, frame, FRAMEPOINT_BOTTOM, positionsX[i], 0.042)
end
local buttonOk = BlzCreateFrameByType("GLUETEXTBUTTON", "Accept Mission", frame, "ScriptDialogButton", 0)
BlzFrameSetPoint(buttonOk, FRAMEPOINT_BOTTOM, frame, FRAMEPOINT_BOTTOM, 0, 0.005)
BlzFrameSetText(buttonOk, "Accept Mission")
uiStoryPanel.ok = buttonOk
local triggerButtonOk = CreateTrigger()
TriggerAddAction(triggerButtonOk, function()
if GetTriggerPlayer() == Player(0) then
hideStoryUI()
else
QuestMessageBJ(GetPlayersAll(), bj_QUESTMESSAGE_UPDATED, GetPlayerName(GetTriggerPlayer()) .. " confirms readiness to play with current settings.")
end
end)
BlzTriggerRegisterFrameEvent(triggerButtonOk, buttonOk, FRAMEEVENT_CONTROL_CLICK)
end
function hideStoryUI()
for i = 1, #uiStoryPanel.storyButtons do
BlzFrameSetVisible(uiStoryPanel.storyButtons[i], false)
end
BlzFrameSetVisible(uiStoryPanel.mainFraim, false)
udg_inSelectedStory = false
end
function showStoryUI()
for i = 1, #uiStoryPanel.storyButtons do
BlzFrameSetVisible(uiStoryPanel.storyButtons[i], true)
end
BlzFrameSetVisible(uiStoryPanel.mainFraim, true)
udg_inSelectedStory = true
end
function createDiffIcon( parent, number, isSelected )
paths = {"easy", "normal", "hard", "veryhard", "hell"}
if isSelected then paths[number] = paths[number] .. "_s" end
paths[number] = "war3mapImported\\" .. paths[number] .. ".tga"
local button = BlzCreateFrameByType("BUTTON", "", parent, "ScoreScreenTabButtonTemplate", 0)
local buttonIconFrame = BlzCreateFrameByType("BACKDROP", "MyIconButtonIcon", button, "", 0)
BlzFrameSetAllPoints(buttonIconFrame, button)
BlzFrameSetSize(button, 0.05, 0.05)
BlzFrameSetTexture(buttonIconFrame, paths[number], 0, false)
local tooltipFrameBackGround = BlzCreateFrame("QuestButtonBaseTemplate", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0, 0)
local tooltipFrameText = BlzCreateFrameByType("TEXT", "MyScriptDialogButtonTooltip", tooltipFrameBackGround, "", 0)
BlzFrameSetSize(tooltipFrameText, 0.25, 0)
BlzFrameSetPoint(tooltipFrameBackGround, FRAMEPOINT_BOTTOMLEFT, tooltipFrameText, FRAMEPOINT_BOTTOMLEFT, -0.01, -0.01)
BlzFrameSetPoint(tooltipFrameBackGround, FRAMEPOINT_TOPRIGHT, tooltipFrameText, FRAMEPOINT_TOPRIGHT, 0.01, 0.01)
BlzFrameSetTooltip(button, tooltipFrameBackGround)
BlzFrameSetPoint(tooltipFrameText, FRAMEPOINT_BOTTOM, button, FRAMEPOINT_TOP, 0, 0.01)
BlzFrameSetEnable(tooltipFrameText, false)
local t = DifParams()[number].descr
BlzFrameSetText(tooltipFrameText, t)
return button, buttonIconFrame
end
function Test2(x, icon, helpText, needCounter)
if icon == nil then icon = "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn" end
local button = BlzCreateFrameByType("BUTTON", "MyIconButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "ScoreScreenTabButtonTemplate", 0)
local buttonIconFrame = BlzCreateFrameByType("BACKDROP", "MyIconButtonIcon", button, "", 0)
BlzFrameSetAllPoints(buttonIconFrame, button)
BlzFrameSetAbsPoint(button, FRAMEPOINT_BOTTOMRIGHT, 0.57-(x-1)*0.03, 0.13)
BlzFrameSetSize(button, 0.028, 0.028)
BlzFrameSetTexture(buttonIconFrame, icon, 0, false)
local counterText = nil
if needCounter == true then
counterText = BlzCreateFrameByType("TEXT", "MyTextFrame", buttonIconFrame, "", 0)
BlzFrameSetText(counterText, "|cffffff001|r")
--BlzFrameSetAllPoints(counterText, buttonIconFrame)
BlzFrameSetPoint(counterText, FRAMEPOINT_BOTTOMRIGHT, buttonIconFrame, FRAMEPOINT_BOTTOMRIGHT, -0.0015, 0.0015)
--BlzFrameSetPoint(tcounterText, FRAMEPOINT_TOPRIGHT, tooltipFrameText, FRAMEPOINT_TOPRIGHT, 0.01, 0.01)
--BlzFrameSetAbsPoint(header, FRAMEPOINT_TOPLEFT, 0.36, 0.53)
BlzFrameSetEnable(counterText, false)
BlzFrameSetScale(counterText, 1)
end
local tooltipFrameText = nil
if helpText ~= nil and helpText ~= "" then
local tooltipFrameBackGround = BlzCreateFrame("QuestButtonBaseTemplate", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0, 0)
tooltipFrameText = BlzCreateFrameByType("TEXT", "MyScriptDialogButtonTooltip", tooltipFrameBackGround, "", 0)
BlzFrameSetSize(tooltipFrameText, 0.25, 0)
BlzFrameSetPoint(tooltipFrameBackGround, FRAMEPOINT_BOTTOMLEFT, tooltipFrameText, FRAMEPOINT_BOTTOMLEFT, -0.01, -0.01)
BlzFrameSetPoint(tooltipFrameBackGround, FRAMEPOINT_TOPRIGHT, tooltipFrameText, FRAMEPOINT_TOPRIGHT, 0.01, 0.01)
BlzFrameSetTooltip(button, tooltipFrameBackGround)
BlzFrameSetPoint(tooltipFrameText, FRAMEPOINT_BOTTOM, button, FRAMEPOINT_TOP, 0, 0.01)
BlzFrameSetEnable(tooltipFrameText, false)
local t = helpText
BlzFrameSetText(tooltipFrameText, t)
end
local trigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(trigger, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(trigger, function()
local frame = BlzGetTriggerFrame()
--print(BlzFrameGetName(frame),"was Clicked")
end)
return buttonIconFrame, tooltipFrameText, counterText
end
function UpdateStoryData(storyId, player)
if player == Player(0) then
BlzFrameSetText(uiStoryPanel.descr, StoryDescrs()[storyId])
BlzFrameSetText(uiStoryPanel.header, StoryHeaders()[storyId])
for i = 1, 5 do
BlzFrameSetVisible(uiStoryPanel.difs[i], storyId == "story1")
end
BlzFrameSetVisible(uiStoryPanel.ok, storyId == "story1")
else
QuestMessageBJ(GetPlayersAll(), bj_QUESTMESSAGE_UPDATED, GetPlayerName(GetTriggerPlayer()) .. " wants the story " .. storyId)
end
end
panelDif = {imageFrame=nil, descrFrame=nil}
function UpdatePanelDif()
if panelDif == nil then
panelDif = {imageFrame=nil, descrFrame=nil}
end
if panelDif.imageFrame == nil then return false end
if selectedDif == nil then return false end
local paths = {"easy", "normal", "hard", "veryhard", "hell"}
local path = "war3mapImported\\" .. paths[selectedDif] .. ".tga"
BlzFrameSetTexture(panelDif.imageFrame, path, 0, false)
BlzFrameSetText(panelDif.descrFrame, Dif().descr)
end
panelSteps = {imageFrame=nil, descrFrame=nil, counterFrame=nil}
function UpdatePanelSteps()
if panelSteps == nil then return false end
BlzFrameSetText(panelSteps.counterFrame, udg_run_left_steps)
end
panelRunInfo = {imageFrame=nil, descrFrame=nil}
function UpdatePanelRunInfo()
if panelRunInfo == nil then return false end
--local txt = "(!) The modified characteristics of mercenaries are updated at the moment of their death and at the moment of their rebirth (temporary flaw)\n\n"
BlzFrameSetText(panelRunInfo.descrFrame, RunInfoStatsText(nil, true))
end
function UICreateSelectedGlory()
storyButtonData = {}
uiStoryPanel = {}
uiStoryPanel.storyButtons = {}
local storyButtonTrigger = CreateTrigger()
TriggerAddAction(storyButtonTrigger, function()
local frame = BlzGetTriggerFrame()
UpdateStoryData( storyButtonData[frame], GetTriggerPlayer() )
end)
panelDif = {imageFrame=nil, descrFrame=nil}
local texture = "ReplaceableTextures\\CommandButtons\\BTNReveal.blp"
panelDif.imageFrame, panelDif.descrFrame = Test2(1, texture, "d", false)
panelSteps = {imageFrame=nil, descrFrame=nil, counterFrame=nil}
local txt2 = "|cffffff00Transitions left|r\nIf the time runs out, the current mission will be considered lost, and your souls will return to the sanctuary to select the next goal."
panelSteps.imageFrame, panelSteps.descrFrame, panelSteps.counterFrame = Test2(2, "war3mapImported\\timer.tga", txt2, true)
panelRunInfo = {imageFrame=nil, descrFrame=nil}
local texture = "ReplaceableTextures\\CommandButtons\\BTNReveal.blp"
panelRunInfo.imageFrame, panelRunInfo.descrFrame = Test2(5, texture, "ddd", false)
local txt3 = [[To win, you must raise the |cffff64ffworld's danger level to 16|r, find the|cffff8080 infernal forge|r there, activate it and survive.
|cffffff00Travel along the yellow paths to raise the world's danger by 1.|r
|cffff0000Travel along the red paths to raise the world's danger by 2.|r
Don't be afraid to lose. In case of defeat, you can improve your souls and try again with additional resources.
At the end of each battle, your mercenaries are fully resurrected and your health is restored.]]
Test2(3, "ReplaceableTextures\\CommandButtons\\BTNScroll.blp", txt3, false)
for i = 3, 11 do
--Test2(i)
end
local b1 = CreateButtonGlory("Story I. Infernal Forge", 1)
local b2 = CreateButtonGlory("Story II. ???", 2)
local b3 = CreateButtonGlory("Story III. ???", 3)
local b4 = CreateButtonGlory("Story IV. ???", 4)
local b5 = CreateButtonGlory("Story V. ???", 5)
local b6 = CreateButtonGlory("Story VI. ???", 6)
uiStoryPanel.storyButtons[1] = b1
uiStoryPanel.storyButtons[2] = b2
uiStoryPanel.storyButtons[3] = b3
uiStoryPanel.storyButtons[4] = b4
uiStoryPanel.storyButtons[5] = b5
uiStoryPanel.storyButtons[6] = b6
for i = 1, 6 do
BlzTriggerRegisterFrameEvent(storyButtonTrigger, uiStoryPanel.storyButtons[i], FRAMEEVENT_CONTROL_CLICK)
end
storyButtonData[b1] = "story1"
storyButtonData[b2] = "story2"
storyButtonData[b3] = "story3"
storyButtonData[b4] = "story4"
storyButtonData[b5] = "story5"
storyButtonData[b6] = "story6"
CreateChapterInfo()
UpdatePanelDif()
UpdatePanelRunInfo()
end
_metaPlayers = nil
function CreateMetaPlayer()
local metaPlayer = {}
local totalLevelHeroes = {}
return metaPlayer
end
function CreateMetaPlayers()
local metaPlayers = {}
metaPlayers = {}
metaPlayers.players = {CreateMetaPlayer(), CreateMetaPlayer(), CreateMetaPlayer(), CreateMetaPlayer()}
return metaPlayers
end
function MetaPlayers()
if _metaPlayers == nil then _metaPlayers = CreateMetaPlayers() end
return _metaPlayers
end
_runInfo = nil
function CreateRunInfo()
local runInfo = {}
runInfo = {}
runInfo.story = nil
runInfo.stage = 0
runInfo.depth = 0
runInfo.currentLocation = {id="", nears={}}
runInfo.mercExtraHP = 0.0
runInfo.mercExtraMP = 0.0
runInfo.mercExtraDamage = 0
runInfo.summonExtraHP = 0.0
runInfo.summonExtraDamage = 0
runInfo.summonExtraDuration = 0.0
runInfo.modXP = 1.0
runInfo.exitBlockers = {}
return runInfo
end
function NewRun( story )
if story == nil then story = ActualStory end
_runInfo = CreateRunInfo()
_runInfo.story = story
RunInfoStatsText()
return _runInfo
end
function CurrentRun()
if _runInfo == nil then _runInfo = CreateRunInfo() end
return _runInfo
end
function RunInfoStatsText(runInfo, isRecurce)
if isRecurce == nil then isRecurce = false end
if runInfo == nil then runInfo = CurrentRun() end
local t = ""
t = t .. "Mercs extra HP |cffffff00" .. runInfo.mercExtraHP .. "|r"
t = t .. "\n" .. "Mercs extra MP |cffffff00" .. runInfo.mercExtraMP .. "|r"
t = t .. "\n" .. "Mercs extra Damage |cffffff00" .. runInfo.mercExtraDamage .. "|r"
t = t .. "\n" .. "Summons extra HP |cffffff00" .. runInfo.summonExtraHP .. "|r"
t = t .. "\n" .. "Summons extra Damage |cffffff00" .. runInfo.summonExtraDamage .. "|r"
t = t .. "\n" .. "Summon extra Duration |cffffff00" .. runInfo.summonExtraDuration .. "|r"
if isRecurce ~= true then UpdatePanelRunInfo() end
local t2 =
[[Mercs extra HP |cffffff00+0%%|r
Mercs extra MP |cffffff00+0%%|r
Mercs extra Damage |cffffff00+0%%|r
Summons extra HP |cffffff00+0%%|r
Summons extra Damage |cffffff00+0|r
Summon extra Duration |cffffff00+0%%|r]]
return t
end
function RunNextDepth( currentDepth )
return CurrentRun().story:NextDepth( currentDepth )
end
function RunIsBlocked()
return #CurrentRun().exitBlockers ~= 0
end
function RunUpgradeMerc(un)
--print(1)
local runInfo = CurrentRun()
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = 0.0
if runInfo.mercExtraHP > 0.0 then
v = udg_run_stats_mercMultHP[playerIndex]
v = v * runInfo.mercExtraHP
v = v + GetUnitStateSwap(UNIT_STATE_MAX_LIFE, un)
BlzSetUnitMaxHP(un, R2I(v))
end
if runInfo.mercExtraMP > 0.0 and GetUnitStateSwap(UNIT_STATE_MAX_MANA, un) > 0 then
v = runInfo.mercExtraMP + GetUnitStateSwap(UNIT_STATE_MAX_MANA, un)
BlzSetUnitMaxMana(un, R2I(v))
end
if runInfo.mercExtraDamage > 0 then
v = udg_run_stats_mercMultDamage[playerIndex]
v = v * runInfo.mercExtraDamage
BlzSetUnitBaseDamage(un, R2I(v + I2R(BlzGetUnitBaseDamage(un, 0))), 0)
BlzSetUnitBaseDamage(un, R2I(v + I2R(BlzGetUnitBaseDamage(un, 1))), 1)
end
--print(10)
--BlzSetUnitBaseDamage(GetTriggerUnit(), (BlzGetUnitWeaponIntegerField(GetTriggerUnit(), UNIT_WEAPON_IF_ATTACK_DAMAGE_BASE, 0) + 1), 0)
--BlzSetUnitRealFieldBJ(GetTriggerUnit(), UNIT_RF_HP, (BlzGetUnitRealField(GetTriggerUnit(), UNIT_RF_HP) + 1))
--BlzSetUnitRealFieldBJ(GetTriggerUnit(), UNIT_RF_MANA, (BlzGetUnitRealField(GetTriggerUnit(), UNIT_RF_MANA) + 1))
end
function RunAddBlocker(idBlocker)
local runInfo = CurrentRun()
for k, v in ipairs(runInfo.exitBlockers) do
if v == idBlocker then return false end
end
table.insert(runInfo.exitBlockers, idBlocker)
SetPlayerTechResearchedSwap(FourCC("R001"), 0, Player(PLAYER_NEUTRAL_PASSIVE))
end
function RunRemoveBlocker(idBlocker)
local runInfo = CurrentRun()
for k, v in ipairs(runInfo.exitBlockers) do
if v == idBlocker then
table.remove(runInfo.exitBlockers, k)
if #runInfo.exitBlockers == 0 then
SetPlayerTechResearchedSwap(FourCC("R001"), 1, Player(PLAYER_NEUTRAL_PASSIVE))
end
return true
end
end
end
function RunInfoAddMercDamage(value)
globalValue = value
function fAddDamage()
local un = GetEnumUnit()
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = udg_run_stats_mercMultDamage[playerIndex]
v = v * globalValue
BlzSetUnitBaseDamage(un, R2I(v + I2R(BlzGetUnitBaseDamage(un, 0))), 0)
BlzSetUnitBaseDamage(un, R2I(v + I2R(BlzGetUnitBaseDamage(un, 1))), 1)
end
ForGroupBJ(udg_mercs_all_mercs_group, fAddDamage)
CurrentRun().mercExtraDamage = CurrentRun().mercExtraDamage + value
RunInfoStatsText()
end
function RunInfoAddMercHP(value)
globalValue = value
function fAddHP()
local un = GetEnumUnit()
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = udg_run_stats_mercMultHP[playerIndex]
v = v * globalValue
v = v + GetUnitStateSwap(UNIT_STATE_MAX_LIFE, un)
BlzSetUnitMaxHP(un, R2I(v))
end
ForGroupBJ(udg_mercs_all_mercs_group, fAddHP)
CurrentRun().mercExtraHP = CurrentRun().mercExtraHP + value
RunInfoStatsText()
end
function RunInfoAddMercMana(value)
globalValue = value
function fAddMP()
local un = GetEnumUnit()
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = globalValue + GetUnitStateSwap(UNIT_STATE_MAX_MANA, un)
BlzSetUnitMaxMana(un, R2I(v))
end
ForGroupBJ(udg_mercs_all_mercs_group, fAddMP)
CurrentRun().mercExtraMP = CurrentRun().mercExtraMP + value
RunInfoStatsText()
end
MainTextFrame = nil
ExtraTextFrame = nil
do
local real = MarkGameStarted
function MarkGameStarted()
real()
local frame = BlzCreateFrameByType("TEXT", "MyTextFrame", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
MainTextFrame = frame
BlzFrameSetText(frame, "|cffffcc00Choose a hero for this race|r")
BlzFrameSetAbsPoint(frame, FRAMEPOINT_CENTER, 0.4, 0.545)
BlzFrameSetEnable(frame, false)
BlzFrameSetScale(frame, 2)
local frame = BlzCreateFrameByType("TEXT", "MyTextFrame", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
ExtraTextFrame = frame
--BlzFrameSetText(frame, "|cff61D9FFAfter each choice, the list of heroes is rerolled|r")
BlzFrameSetText(frame, "|cff61D9FF(After each choice, the list of heroes is rerolled)|r")
BlzFrameSetAbsPoint(frame, FRAMEPOINT_CENTER, 0.4, 0.53)
BlzFrameSetEnable(frame, false)
BlzFrameSetScale(frame, 1)
end
end
function SetMainText( player, txt )
txt = "|cffffcc00" .. txt .. "|r"
--txt2 = "|cff61D9FFDanger rank: ??|r"
if GetLocalPlayer() == player then
BlzFrameSetText(MainTextFrame, txt)
end
end
function SetExtraText( player, txt )
txt = "|cff61D9FF" .. txt .. "|r"
if GetLocalPlayer() == player then
BlzFrameSetText(ExtraTextFrame, txt)
end
end
do
local real = MarkGameStarted
function MarkGameStarted()
real()
-- allow to generate Frames from ui\framedef\ui\escmenutemplates.fdf
BlzLoadTOCFile("war3mapImported\\Templates.toc")
-- create a hidden Frame a container for all
local windowcontainerFrame = BlzCreateFrameByType("FRAME", "", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
-- create a box as child of the container
local boxFrame = BlzCreateFrameByType("BACKDROP", "", windowcontainerFrame, "EscMenuBackdrop", 0)
BlzFrameSetSize(boxFrame, 0.4, 0.4)
BlzFrameSetAbsPoint(boxFrame, FRAMEPOINT_CENTER, 0.4, 0.3)
-- The option to close (hide) the box
local closeButton = BlzCreateFrameByType("GLUETEXTBUTTON", "", boxFrame, "ScriptDialogButton", 0)
BlzFrameSetSize(closeButton, 0.03, 0.03)
BlzFrameSetText(closeButton, "X")
BlzFrameSetPoint(closeButton, FRAMEPOINT_TOPRIGHT, boxFrame, FRAMEPOINT_TOPRIGHT, 0, 0)
-- this trigger handles clicking the close Button, it hides the Logical super Parent when the close Button is clicked for the clicking Player.
local closeTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(closeTrigger, closeButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(closeTrigger, function()
if GetLocalPlayer() == GetTriggerPlayer() then
BlzFrameSetVisible(windowcontainerFrame, false)
end
end)
-- Because one can close (hide) the box, one also should be able to show it again, this is done with an button that is only visible while the player is in Menu (F10)
local showButton = BlzCreateFrameByType("GLUETEXTBUTTON", "", BlzGetFrameByName("InsideMainPanel",0), "ScriptDialogButton", 0)
BlzFrameSetSize(showButton, 0.08, 0.04)
BlzFrameSetText(showButton, "show Info Frame")
BlzFrameSetAbsPoint(showButton, FRAMEPOINT_BOTTOMLEFT, 0, 0.2)
local showTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(showTrigger, showButton, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(showTrigger, function()
if GetLocalPlayer() == GetTriggerPlayer() then
BlzFrameSetVisible(windowcontainerFrame, true)
end
end)
-- create 3 Buttons based on ScriptDialogButton
local buttonPage1 = BlzCreateFrameByType("GLUETEXTBUTTON", "", boxFrame, "ScriptDialogButton", 0)
local buttonPage2 = BlzCreateFrameByType("GLUETEXTBUTTON", "", boxFrame, "ScriptDialogButton", 0)
local buttonPage3 = BlzCreateFrameByType("GLUETEXTBUTTON", "", boxFrame, "ScriptDialogButton", 0)
BlzFrameSetSize(buttonPage1, 0.08, 0.03)
BlzFrameSetSize(buttonPage2, 0.08, 0.03)
BlzFrameSetSize(buttonPage3, 0.08, 0.03)
BlzFrameSetText(buttonPage1, "Page 1")
BlzFrameSetText(buttonPage2, "Page 2")
BlzFrameSetText(buttonPage3, "Page 3")
-- pos the 2.Button at the bottom
BlzFrameSetPoint(buttonPage2, FRAMEPOINT_BOTTOM, boxFrame, FRAMEPOINT_BOTTOM, 0, 0.017)
-- the 3. Button to it's right
BlzFrameSetPoint(buttonPage3, FRAMEPOINT_LEFT, buttonPage2, FRAMEPOINT_RIGHT, 0, 0)
-- the 1. Button to it's left
BlzFrameSetPoint(buttonPage1, FRAMEPOINT_RIGHT, buttonPage2, FRAMEPOINT_LEFT, 0, 0)
-- Now we got the button in order: 1 2 3, but don't have to bother the real position and sizes
-- create a Trigger handling the Page Button Clicks
local pageTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(pageTrigger, buttonPage1, FRAMEEVENT_CONTROL_CLICK)
BlzTriggerRegisterFrameEvent(pageTrigger, buttonPage2, FRAMEEVENT_CONTROL_CLICK)
BlzTriggerRegisterFrameEvent(pageTrigger, buttonPage3, FRAMEEVENT_CONTROL_CLICK)
-- create a container Frame for each Page, this frames are mostly logical. They simple down the page swapping a lot.
local containerFramePage1 = BlzCreateFrameByType("FRAME", "", boxFrame, "", 0)
local containerFramePage2 = BlzCreateFrameByType("FRAME", "", boxFrame, "", 0)
local containerFramePage3 = BlzCreateFrameByType("FRAME", "", boxFrame, "", 0)
-- the containers are a bit smaller than the box to avoid colisions with the border and the buttons at the bottom
-- the containers are given a size to place contentFrames relative to the containers. This allows to place it to any valid coords and it will still work as wanted.
BlzFrameSetSize(containerFramePage1, 0.36, 0.3)
BlzFrameSetSize(containerFramePage2, 0.36, 0.3)
BlzFrameSetSize(containerFramePage3, 0.36, 0.3)
-- place the containers to the center of the box
BlzFrameSetPoint(containerFramePage1, FRAMEPOINT_TOP, boxFrame, FRAMEPOINT_TOP, 0, -0.03)
BlzFrameSetPoint(containerFramePage2, FRAMEPOINT_TOP, boxFrame, FRAMEPOINT_TOP, 0, -0.03)
BlzFrameSetPoint(containerFramePage3, FRAMEPOINT_TOP, boxFrame, FRAMEPOINT_TOP, 0, -0.03)
-- hide the page containers on default
BlzFrameSetVisible(containerFramePage1, false)
BlzFrameSetVisible(containerFramePage2, false)
BlzFrameSetVisible(containerFramePage3, false)
TriggerAddAction(pageTrigger, function()
local clickedButton = BlzGetTriggerFrame()
-- only the active player should be affected
if GetLocalPlayer() == GetTriggerPlayer() then
-- hide all pages, could be optimized monitoring the current selected but won't do that in this example.
BlzFrameSetVisible(containerFramePage1, false)
BlzFrameSetVisible(containerFramePage2, false)
BlzFrameSetVisible(containerFramePage3, false)
-- show the page based on the clicked button.
if clickedButton == buttonPage1 then
BlzFrameSetVisible(containerFramePage1, true)
elseif clickedButton == buttonPage2 then
BlzFrameSetVisible(containerFramePage2, true)
elseif clickedButton == buttonPage3 then
BlzFrameSetVisible(containerFramePage3, true)
end
end
end)
-- Page 1 Content
-- A page that shows some text about uther taken from https://wow.gamepedia.com/Uther_the_Lightbringer
local parent = containerFramePage1
local frame = BlzCreateFrameByType("BACKDROP", "", parent, "", 0)
BlzFrameSetSize(frame, 0.04, 0.04)
BlzFrameSetPoint(frame, FRAMEPOINT_TOPLEFT, parent, FRAMEPOINT_TOPLEFT, 0, 0)
BlzFrameSetTexture(frame, "ReplaceableTextures\\CommandButtons\\BTNHeroPaladin", 0, false)
frame = BlzCreateFrameByType("TEXT", "", parent, "", 0)
BlzFrameSetPoint(frame, FRAMEPOINT_TOPRIGHT, parent, FRAMEPOINT_TOPRIGHT, 0, 0)
BlzFrameSetText(frame, "Paladin of the Silver Hand")
BlzFrameSetScale(frame, 1.2)
frame = BlzCreateFrameByType("TEXT", "", parent, "", 0)
BlzFrameSetPoint(frame, FRAMEPOINT_TOPRIGHT, parent, FRAMEPOINT_TOPRIGHT, 0, -0.02)
BlzFrameSetText(frame, "Uther")
BlzFrameSetScale(frame, 1.3)
frame = BlzCreateFrameByType("TEXTAREA", "", parent, "EscMenuTextAreaTemplate", 0)
BlzFrameSetPoint(frame, FRAMEPOINT_BOTTOMRIGHT, parent, FRAMEPOINT_BOTTOMRIGHT, 0, 0)
BlzFrameSetPoint(frame, FRAMEPOINT_TOPLEFT, parent, FRAMEPOINT_TOPLEFT, 0, -0.05)
BlzFrameSetText(frame, "Lord Uther the Lightbringer, or Sire Uther Lightbringer, was the first of the five paladins of the Knights of the Silver Hand along with Turalyon, Saidan Dathrohan, Tirion Fordring, and Gavinrad the Dire. He led his order in the battle against the Horde during the Second War. During the Third War, Uther was betrayed and murdered by his beloved pupil, Prince Arthas, while defending the urn carrying the ashes of Arthas' father, King Terenas. After death, his soul was deemed worthy of entering the plane of Bastion within the Shadowlands. ")
BlzFrameAddText(frame, "|cffffcc00Personality|r|nThough zealous and weathered, Uther's eyes show kindness and wisdom. He is Lordaeron's self-appointed defender, but regrets that violence is the only way to solve some problems. Possessing a rich, commanding voice and great physical strength, Uther is also capable of gentleness and compassion, though he does not suffer fools. He is the epitome of the paladin warrior — a mighty foe to his enemies and a bastion of hope to his allies.")
BlzFrameAddText(frame, "|cffffcc00In combat|r|nUther strides directly into melee, placing himself in the center of the most brutal combat. He places himself in danger to spare his allies. He is at his peak against demons and undead, and brings his full array of spells and abilities to bear against these creatures — smites, banishing strikes, power turning, searing light from his hammer, hooks of binding and dispel evil. He uses lay on hands to blast undead that resist his hammer. Against truly mighty opponents, Uther attacks with his Big Smash feat. He prefers leading others into battle but fights alone if the situation warrants. Uther endangers himself to help others if he must, and is willing to sacrifice himself for others — but he does not do so foolishly, as he knows how valuable he is to Lordaeron")
BlzFrameAddText(frame, "|cffffcc00Equipment|r")
BlzFrameAddText(frame, "|cffffcc00Hammer of the Lightbringer|r|nThe two-handed hammer’s haft is polished mahogany, while the head is adamantine. A silver hand emblem rests in a bed of gold design on either side. This mighty weapon was forged when Archbishop Faol created the Knights of the Silver Hand, and the archbishop bequeathed it to the order’s first Grand Master — Uther the Lightbringer. A group of paladins recovered the hammer after Uther's death, but none has thought himself worthy of carrying the legendary weapon.")
BlzFrameAddText(frame, "|cffffcc00Gloves of the Silver Hand|r|nUther Lightbringer is said to have been the first to enchant these gloves to aid him in the battle against the Scourge. These are large, padded leather and mail gloves bleached pure white with the holy symbols of the Silver Hand burned into the palms. Although they are large mail items, they are remarkably light.")
BlzFrameAddText(frame, "Although the original shroud that covered the fallen paladin Uther Lightbringer of the Knights of the Silver Hand was lost years ago in frequent skirmishes between the Scourge and the Alliance, rumors of the shroud remain. Some of the remaining priests of the Holy Light have infused linen with power in honor of their fallen champion. These shrouds are made of soft, white linen, about 6 feet by 3 feet. The divine magic used to create these creates a gray image of a dead paladin, usually the face of the creator of the shroud.")
BlzFrameAddText(frame, "Uther is universally known as the Lightbringer but he hasn't been always addressed as such. In fact, it was General Turalyon who got the idea of this nickname after seeing the inspiration that the leader of the Silver Hand had on his men. When he was charged by Khadgar and Uther as Supreme Commander of the Alliance he responded: \"And I thank you, Uther the Lightbringer, \" Turalyon replied, and he saw the older Paladin's eyes widen at the new title. \"For so shall you be known henceforth, in honor of the Holy Light you brought us this day.\" Uther bowed, clearly pleased, then turned without another word and walked back toward the other knights of the Silver Hand, no doubt to tell them their marching orders.")
-- Page 2 Content
-- a col of custom Buttons this Page is bad done one actually should define a function to prevent the repeating same lines, but I won't do that in this example.
parent = containerFramePage2
local buttonTrigger = CreateTrigger()
local buttonData = {}
TriggerAddAction(buttonTrigger, function()
CreateUnit(GetTriggerPlayer(), buttonData[BlzGetTriggerFrame()], 0, 0, 0)
end)
local prevFrame, button, icon, text, unitId
unitId = FourCC("Hpal")
button = BlzCreateFrameByType("BUTTON", "", parent, "IconButtonTemplate", 0)
icon = BlzCreateFrameByType("BACKDROP", "", button, "", 0)
text = BlzCreateFrameByType("TEXT", "", button, "", 0)
buttonData[button] = unitId
BlzTriggerRegisterFrameEvent(buttonTrigger, button, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetEnable(text, false)
BlzFrameSetSize(button, 0.2, 0.05)
BlzFrameSetSize(icon, 0.04, 0.04)
BlzFrameSetPoint(icon, FRAMEPOINT_LEFT, button, FRAMEPOINT_LEFT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_LEFT, icon, FRAMEPOINT_RIGHT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_RIGHT, button, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetTexture(icon, BlzGetAbilityIcon(unitId), 0, false)
BlzFrameSetText(text, GetObjectName(unitId))
BlzFrameSetPoint(button, FRAMEPOINT_TOP, parent, FRAMEPOINT_TOP, 0, 0)
prevFrame = button
unitId = FourCC("Hamg")
button = BlzCreateFrameByType("BUTTON", "", parent, "IconButtonTemplate", 0)
icon = BlzCreateFrameByType("BACKDROP", "", button, "", 0)
text = BlzCreateFrameByType("TEXT", "", button, "", 0)
buttonData[button] = unitId
BlzTriggerRegisterFrameEvent(buttonTrigger, button, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetEnable(text, false)
BlzFrameSetSize(button, 0.2, 0.05)
BlzFrameSetSize(icon, 0.04, 0.04)
BlzFrameSetPoint(icon, FRAMEPOINT_LEFT, button, FRAMEPOINT_LEFT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_LEFT, icon, FRAMEPOINT_RIGHT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_RIGHT, button, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetTexture(icon, BlzGetAbilityIcon(unitId), 0, false)
BlzFrameSetText(text, GetObjectName(unitId))
BlzFrameSetPoint(button, FRAMEPOINT_TOP, prevFrame, FRAMEPOINT_BOTTOM, 0, 0)
prevFrame = button
unitId = FourCC("Hmkg")
button = BlzCreateFrameByType("BUTTON", "", parent, "IconButtonTemplate", 0)
icon = BlzCreateFrameByType("BACKDROP", "", button, "", 0)
text = BlzCreateFrameByType("TEXT", "", button, "", 0)
buttonData[button] = unitId
BlzTriggerRegisterFrameEvent(buttonTrigger, button, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetEnable(text, false)
BlzFrameSetSize(button, 0.2, 0.05)
BlzFrameSetSize(icon, 0.04, 0.04)
BlzFrameSetPoint(icon, FRAMEPOINT_LEFT, button, FRAMEPOINT_LEFT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_LEFT, icon, FRAMEPOINT_RIGHT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_RIGHT, button, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetTexture(icon, BlzGetAbilityIcon(unitId), 0, false)
BlzFrameSetText(text, GetObjectName(unitId))
BlzFrameSetPoint(button, FRAMEPOINT_TOP, prevFrame, FRAMEPOINT_BOTTOM, 0, 0)
prevFrame = button
unitId = FourCC("Hblm")
button = BlzCreateFrameByType("BUTTON", "", parent, "IconButtonTemplate", 0)
icon = BlzCreateFrameByType("BACKDROP", "", button, "", 0)
text = BlzCreateFrameByType("TEXT", "", button, "", 0)
buttonData[button] = unitId
BlzTriggerRegisterFrameEvent(buttonTrigger, button, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetEnable(text, false)
BlzFrameSetSize(button, 0.2, 0.05)
BlzFrameSetSize(icon, 0.04, 0.04)
BlzFrameSetPoint(icon, FRAMEPOINT_LEFT, button, FRAMEPOINT_LEFT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_LEFT, icon, FRAMEPOINT_RIGHT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_RIGHT, button, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetTexture(icon, BlzGetAbilityIcon(unitId), 0, false)
BlzFrameSetText(text, GetObjectName(unitId))
BlzFrameSetPoint(button, FRAMEPOINT_TOP, prevFrame, FRAMEPOINT_BOTTOM, 0, 0)
prevFrame = button
unitId = FourCC("Hvwd")
button = BlzCreateFrameByType("BUTTON", "", parent, "IconButtonTemplate", 0)
icon = BlzCreateFrameByType("BACKDROP", "", button, "", 0)
text = BlzCreateFrameByType("TEXT", "", button, "", 0)
buttonData[button] = unitId
BlzTriggerRegisterFrameEvent(buttonTrigger, button, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetEnable(text, false)
BlzFrameSetSize(button, 0.2, 0.05)
BlzFrameSetSize(icon, 0.04, 0.04)
BlzFrameSetPoint(icon, FRAMEPOINT_LEFT, button, FRAMEPOINT_LEFT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_LEFT, icon, FRAMEPOINT_RIGHT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_RIGHT, button, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetTexture(icon, BlzGetAbilityIcon(unitId), 0, false)
BlzFrameSetText(text, GetObjectName(unitId))
BlzFrameSetPoint(button, FRAMEPOINT_TOP, prevFrame, FRAMEPOINT_BOTTOM, 0, 0)
prevFrame = button
unitId = FourCC("Hjai")
button = BlzCreateFrameByType("BUTTON", "", parent, "IconButtonTemplate", 0)
icon = BlzCreateFrameByType("BACKDROP", "", button, "", 0)
text = BlzCreateFrameByType("TEXT", "", button, "", 0)
buttonData[button] = unitId
BlzTriggerRegisterFrameEvent(buttonTrigger, button, FRAMEEVENT_CONTROL_CLICK)
BlzFrameSetEnable(text, false)
BlzFrameSetSize(button, 0.2, 0.05)
BlzFrameSetSize(icon, 0.04, 0.04)
BlzFrameSetPoint(icon, FRAMEPOINT_LEFT, button, FRAMEPOINT_LEFT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_LEFT, icon, FRAMEPOINT_RIGHT, 0.01, 0)
BlzFrameSetPoint(text, FRAMEPOINT_RIGHT, button, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetTexture(icon, BlzGetAbilityIcon(unitId), 0, false)
BlzFrameSetText(text, GetObjectName(unitId))
BlzFrameSetPoint(button, FRAMEPOINT_TOP, prevFrame, FRAMEPOINT_BOTTOM, 0, 0)
prevFrame = button
-- Page 3 Content
parent = containerFramePage3
local editBox1, editBox2, result, editboxTrigger
editBox1 = BlzCreateFrameByType("EDITBOX", "", parent, "EscMenuEditBoxTemplate", 0)
editBox2 = BlzCreateFrameByType("EDITBOX", "", parent, "EscMenuEditBoxTemplate", 0)
result = BlzCreateFrameByType("TEXT", "", parent, "", 0)
BlzFrameSetText(result, "Result")
BlzFrameSetScale(result, 1.4)
BlzFrameSetSize(editBox1, 0.1, 0.04)
BlzFrameSetSize(editBox2, 0.1, 0.04)
BlzFrameSetPoint(editBox1, FRAMEPOINT_TOPLEFT, parent, FRAMEPOINT_TOPLEFT, 0, 0)
BlzFrameSetPoint(editBox2, FRAMEPOINT_TOPLEFT, editBox1, FRAMEPOINT_TOPRIGHT, 0, 0)
BlzFrameSetPoint(result, FRAMEPOINT_BOTTOM, parent, FRAMEPOINT_BOTTOM, 0, 0)
button = BlzCreateFrameByType("GLUETEXTBUTTON", "", parent, "ScriptDialogButton", 0)
BlzFrameSetSize(button, 0.03, 0.03)
BlzFrameSetText(button, "+")
BlzFrameSetPoint(button, FRAMEPOINT_LEFT, parent, FRAMEPOINT_LEFT, 0, 0)
editboxTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(editboxTrigger, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(editboxTrigger, function()
BlzFrameSetText(result, (tonumber(BlzFrameGetText(editBox1)) + tonumber(BlzFrameGetText(editBox2))))
end)
prevFrame = button
button = BlzCreateFrameByType("GLUETEXTBUTTON", "", parent, "ScriptDialogButton", 0)
BlzFrameSetSize(button, 0.03, 0.03)
BlzFrameSetText(button, "-")
BlzFrameSetPoint(button, FRAMEPOINT_LEFT, prevFrame, FRAMEPOINT_RIGHT, 0, 0)
editboxTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(editboxTrigger, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(editboxTrigger, function()
BlzFrameSetText(result, (tonumber(BlzFrameGetText(editBox1)) - tonumber(BlzFrameGetText(editBox2))))
end)
prevFrame = button
button = BlzCreateFrameByType("GLUETEXTBUTTON", "", parent, "ScriptDialogButton", 0)
BlzFrameSetSize(button, 0.03, 0.03)
BlzFrameSetText(button, "*")
BlzFrameSetPoint(button, FRAMEPOINT_LEFT, prevFrame, FRAMEPOINT_RIGHT, 0, 0)
editboxTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(editboxTrigger, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(editboxTrigger, function()
BlzFrameSetText(result, (tonumber(BlzFrameGetText(editBox1)) * tonumber(BlzFrameGetText(editBox2))))
end)
prevFrame = button
button = BlzCreateFrameByType("GLUETEXTBUTTON", "", parent, "ScriptDialogButton", 0)
BlzFrameSetSize(button, 0.03, 0.03)
BlzFrameSetText(button, "/")
BlzFrameSetPoint(button, FRAMEPOINT_LEFT, prevFrame, FRAMEPOINT_RIGHT, 0, 0)
editboxTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(editboxTrigger, button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddAction(editboxTrigger, function()
BlzFrameSetText(result, (tonumber(BlzFrameGetText(editBox1)) / tonumber(BlzFrameGetText(editBox2))))
end)
prevFrame = button
print("done")
end
end
modesById = {}
modesByIndex = {}
modesInPanels = {[1]={}, [2]={}, [3]={}}
selecedModeFrame = nil
selectedModes = {}
uiFrameModes = {}
uiFrameTextModes = {}
function setSelectedModes(modes)
selectedModes = {}
for modeIndex = 1, #modesByIndex do
--table.insert(selectedModes, {mode=mode, isOn=false})
local mode = modesByIndex[modeIndex]
selectedModes[mode.id] = {mode=mode, isOn=false}
end
for modeIndex = 1, #modes do
local mode = modes[modeIndex]
selectedModes[mode.id].isOn = true
--print(mode.id .. " " .. mode.name .. " is on")
end
getSelectedModesText(true)
end
function getActualModes()
local modes = {}
for modeIndex = 1, #modesByIndex do
local mode = modesByIndex[modeIndex]
if selectedModes[mode.id].isOn then
table.insert(modes, mode)
end
end
return modes
end
function getSelectedModesText(needDisplay)
local txt = "|cffffcc00Selected modes:|r"
if needDisplay then DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,15,txt) end
local i = 0
for modeIndex = 1, #modesByIndex do
local mode = modesByIndex[modeIndex]
if selectedModes[mode.id].isOn then
i = i + 1
txt2 = " #" .. i .. " |cffffcc00" .. mode.name .. " -|r " .. mode.descr
txt = txt .. "|n" .. txt2
if needDisplay then DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,15,txt2) end
end
end
return txt
end
function displayModeById(id)
local mode = modesById[id]
DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,15,"|cffffcc00New world modes!|r")
local txt = "|cffffcc00" .. mode.name .. " -|r " .. mode.descr
DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,15,txt)
end
function modeIsActive(mode)
return selectedModes[mode.id].isOn
end
function addMode(data)
modesById[data.id] = data
table.insert(modesByIndex, data)
end
function getRandMode()
return modesByIndex[math.random(1, #modesByIndex)]
end
function countNotActiveModes()
local i = 0
for modeIndex = 1, #modesByIndex do
local mode = modesByIndex[modeIndex]
if not selectedModes[mode.id].isOn then
i = i + 1
end
end
return i
end
function getRandNotActiveModeId()
local i = false
local mode = 0
if countNotActiveModes() <= 0 then return "" end
while not i do
mode = getRandMode()
i = not modeIsActive(mode)
end
return mode.id
end
function getRandModes(count)
local appruve = false
local ret = {}
while not appruve do
appruve = true
ret = {}
for x = 1, count do
ret[x] = modesByIndex[math.random(1, #modesByIndex)]
for y = 1, x-1 do
if ret[x] == ret[y] then appruve = false end
end
end
end
return ret
end
function modeIdActivate(id)
local mode = modesById[id]
selectedModes[id].isOn = true
QuestSetDescription(udg_modesQuest, getSelectedModesText(false))
CreateQuestItemBJ(udg_modesQuest, mode.name)
displayModeById(id)
end
function fillModeValues()
addMode({
id="moreKnowledges",
name="Age of knowledge",
descr="Find 2 altars of knowledge instead of one"
})
addMode({
id="extraTavern",
name="Binge drinking",
descr="In any location there will be a bonus tavern with a 20%% chance"
})
addMode({
id="championSummon",
name="Channel",
descr="Whenever someone summons a minion, 25%% of the time it will summon a champion version of it with 50%% bonuses to damage and health."
})
addMode({
id="orkReputation",
name="Headhunting",
descr="Get 2 reputation for destroying Orc bases. For the destruction of each tower you are given 50 coins."
})
addMode({
id="creepsResuration",
name="The Restless",
descr="Approximately 30%% of enemies have the ability to be Reincarnation. +25%% to the wood received for clearing the area."
})
addMode({
id="bossesHunter",
name="The Witcher",
descr="Killing mini-bosses gives +2 steps to old age and 5 random stat books"
})
addMode({
id="creepsClones",
name="Clones",
descr="Approximately 30%% of enemies have the ability of simple cloning and an additional 200 mana for units. +25%% to the wood received for clearing the area."
})
addMode({
id="xpFromMercs",
name="Mentoring",
descr="When moving to a new location, each player’s hero receives 15 experience for each troop limit occupied"
})
--addMode({
--id="completionAct",
--name="Target",
--descr="Upon completion of the act, receive +5 to steps to old age and +250 to wood"
--})
addMode({
id="eraPower",
name="Era of power",
descr="When using a spell, there is a 20%% chance that the spent mana will be restored and the recovery cooldown will be reset. It works on both friends and strangers."
})
addMode({
id="resilientSouls",
name="Resilient souls",
--descr="85%% chance for heroes, and 70%% for other units that they will not die when receiving a lethal attack"
descr="40%% chance for enemy units and heroes, 55%% chance for your units and 90%% chance for your heroes to avoid fatal damage."
})
addMode({
id="vengefulSouls",
name="Vengeful souls",
descr="Any unit deals up to 35%% more damage depending on its lack of health."
})
addMode({
id="soulAbsorption",
name="Soul absorption",
descr="After death, enemies leave behind a magic rune in 15%% of cases."
})
addMode({
id="penetratingMagic",
name="Penetrating Magic",
descr="All enemies and mercenaries lose invulnerability to magic. Instead, they gain 75%% magic resistance."
})
addMode({
id="siegeCraft",
name="Siege Craft",
descr="For a successful defense of castles, the reward in the form of an upgrade of creatures by 50%% is higher, but there are also 20%% more besiegers."
})
addMode({
id="moreMercs",
name="Mercenaries Paradise",
descr="All buildings with mercenaries have more mercenaries."
})
addMode({
id="extraMarkets",
name="Shopping Festival",
descr="In any location there will be a bonus market with a 20%% chance"
})
--addMode({
--id="ageOfAssassins",
--name="Age of Assassins",
--descr="When someone attacks from behind, they deal more damage. Damage increases from 10%% to 25%% from a 90 degree side hit to a 180 degree full back hit."
--})
--addMode({
--id="evolution",
--name="Uncontrolled evolution",
--descr="When moving to a new location, each mercenary has a 20%% chance of becoming a random mercenary of the next level if the limit is enough."
--})
end
do
local real = MarkGameStarted
function MarkGameStarted()
real()
fillModeValues()
-- allow to generate Frames from ui\framedef\ui\escmenutemplates.fdf
BlzLoadTOCFile("war3mapImported\\Templates.toc")
-- create a hidden Frame a container for all
local windowcontainerFrame = BlzCreateFrameByType("FRAME", "", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
selecedModeFrame = windowcontainerFrame
-- create a box as child of the container
local boxFrame = BlzCreateFrameByType("BACKDROP", "", windowcontainerFrame, "EscMenuBackdrop", 0)
BlzFrameSetSize(boxFrame, 0.22, 0.32)
BlzFrameSetAbsPoint(boxFrame, FRAMEPOINT_CENTER, 0.17, 0.35)
local boxFrame2 = BlzCreateFrameByType("BACKDROP", "", windowcontainerFrame, "EscMenuBackdrop", 0)
BlzFrameSetSize(boxFrame2, 0.22, 0.32)
BlzFrameSetAbsPoint(boxFrame2, FRAMEPOINT_CENTER, 0.4, 0.35)
local boxFrame3 = BlzCreateFrameByType("BACKDROP", "", windowcontainerFrame, "EscMenuBackdrop", 0)
BlzFrameSetSize(boxFrame3, 0.22, 0.32)
BlzFrameSetAbsPoint(boxFrame3, FRAMEPOINT_CENTER, 0.8-0.17, 0.35)
uiFrameModes[1] = boxFrame
uiFrameModes[2] = boxFrame2
uiFrameModes[3] = boxFrame3
function but(fr, txt, panelIndex)
frame = BlzCreateFrameByType("TEXTAREA", "", fr, "EscMenuTextAreaTemplate", 0)
BlzFrameSetPoint(frame, FRAMEPOINT_BOTTOMRIGHT, fr, FRAMEPOINT_BOTTOMRIGHT, 0.034, -0.02)
BlzFrameSetPoint(frame, FRAMEPOINT_TOPLEFT, fr, FRAMEPOINT_TOPLEFT, 0.018, -0.02) -- -0.02
uiFrameTextModes[panelIndex] = frame
local buttonPage1 = BlzCreateFrameByType("GLUETEXTBUTTON", "", fr, "ScriptDialogButton", 0)
BlzFrameSetSize(buttonPage1, 0.08, 0.03)
BlzFrameSetText(buttonPage1, txt)
BlzFrameSetPoint(buttonPage1, FRAMEPOINT_BOTTOM, fr, FRAMEPOINT_BOTTOM, 0, 0.017)
local fakebutton = BlzCreateFrameByType("TEXTAREA", "", fr, "EscMenuTextAreaTemplate", 0)
BlzFrameSetSize(fakebutton, 0.08, 0.03)
--BlzFrameSetPoint(fakebutton, FRAMEPOINT_BOTTOM, fr, FRAMEPOINT_BOTTOM, 0, 0.017)
BlzFrameSetPoint(fakebutton, FRAMEPOINT_BOTTOMRIGHT, fr, FRAMEPOINT_BOTTOMRIGHT, 0.034, -0.02)
BlzFrameSetPoint(fakebutton, FRAMEPOINT_TOPLEFT, fr, FRAMEPOINT_BOTTOMLEFT, 0.034, 0.05) -- -0.02
BlzFrameSetText(fakebutton, "|cffff8080Player 1|r |cffffc800must make a choice|r")
BlzFrameSetVisible(buttonPage1, Player(0) == GetLocalPlayer())
BlzFrameSetVisible(fakebutton, Player(0) ~= GetLocalPlayer())
local showTrigger = CreateTrigger()
BlzTriggerRegisterFrameEvent(showTrigger, buttonPage1, FRAMEEVENT_CONTROL_CLICK)
if panelIndex == 1 then
TriggerAddAction(showTrigger, function()
BlzFrameSetVisible(selecedModeFrame, false)
setSelectedModes(modesInPanels[1])
udg_isActiveSelectedMode = false
end)
end
if panelIndex == 2 then
TriggerAddAction(showTrigger, function()
BlzFrameSetVisible(selecedModeFrame, false)
setSelectedModes(modesInPanels[2])
udg_isActiveSelectedMode = false
end)
end
if panelIndex == 3 then
TriggerAddAction(showTrigger, function()
BlzFrameSetVisible(selecedModeFrame, false)
setSelectedModes(modesInPanels[3])
udg_isActiveSelectedMode = false
end)
end
return buttonPage1
end
but(uiFrameModes[1], "Choose", 1)
but(uiFrameModes[2], "Choose", 2)
but(uiFrameModes[3], "Choose", 3)
--updateModePanels()
BlzFrameSetVisible(selecedModeFrame, false)
udg_isActiveSelectedMode = false
end
end
function updateModePanel(panelIndex)
frame = uiFrameTextModes[panelIndex]
BlzFrameSetText(frame, "|cffbbcc00" .. " Modes list #" .. panelIndex .. "|r")
local modes = getRandModes(2)
modesInPanels[panelIndex] = modes
for modeIndex = 1, #modes do
local mode = modes[modeIndex]
BlzFrameAddText(frame, "|cffffcc00" .. mode.name .. "|r")
BlzFrameAddText(frame, mode.descr .. "|n")
end
end
function updateModePanels()
for i = 1, 3 do
updateModePanel(i)
end
BlzFrameSetVisible(selecedModeFrame, true)
udg_isActiveSelectedMode = true
end
function SummonMode( un )
local p = GetOwningPlayer(un)
if p == Player(0) or p == Player(1) or p == Player(2) or p == Player(3) then
local runInfo = CurrentRun()
BlzSetUnitMaxHP(un, R2I((GetUnitStateSwap(UNIT_STATE_MAX_LIFE, un) + runInfo.summonExtraHP)))
SetUnitLifePercentBJ(un, 100)
--BlzSetUnitBaseDamage(un, 1500000, 0)
--BlzSetUnitBaseDamage(un, 1500000, 1)
for i = 0, 1 do
local damage = BlzGetUnitWeaponIntegerField(un, UNIT_WEAPON_IF_ATTACK_DAMAGE_BASE, i)
damage = R2I(damage + runInfo.summonExtraDamage)
BlzSetUnitBaseDamage(un, damage, i)
--damage = BlzGetUnitWeaponIntegerField(un, UNIT_WEAPON_IF_ATTACK_DAMAGE_SIDES_PER_DIE, i)
--damage = R2I(damage * 1.50)
--BlzSetUnitDiceSides(un, damage, i)
end
end
if selectedModes.championSummon.isOn and math.random() < 0.25 then
BlzSetUnitMaxHP(un, R2I((GetUnitStateSwap(UNIT_STATE_MAX_LIFE, un) * 1.50)))
SetUnitLifePercentBJ(un, 100)
for i = 0, 1 do
local damage = BlzGetUnitWeaponIntegerField(un, UNIT_WEAPON_IF_ATTACK_DAMAGE_BASE, i)
damage = R2I(damage * 1.50)
BlzSetUnitBaseDamage(un, damage, i)
damage = BlzGetUnitWeaponIntegerField(un, UNIT_WEAPON_IF_ATTACK_DAMAGE_SIDES_PER_DIE, i)
damage = R2I(damage * 1.50)
BlzSetUnitDiceSides(un, damage, i)
end
local scale = BlzGetUnitRealField(un, UNIT_RF_SCALING_VALUE) * 135.00
SetUnitScalePercent(un, scale, scale, scale)
SetUnitVertexColorBJ(un, 100, 80.00, 20.00, 0)
end
end
do; local _, codeLoc = pcall(error, "", 2) --get line number where DebugUtils begins.
--[[
--------------------------
-- | Debug Utils 2.0a | --
--------------------------
--> https://www.hiveworkshop.com/threads/debug-utils-ingame-console-etc.330758/
- by Eikonium, with special thanks to:
- @Bribe, for pretty table print, showing that xpcall's message handler executes before the stack unwinds and useful suggestions like name caching and stack trace improvements.
- @Jampion, for useful suggestions like print caching and applying Debug.try to all code entry points
- @Luashine, for useful feedback and building "WC3 Debug Console Paste Helper" (https://github.com/Luashine/wc3-debug-console-paste-helper#readme)
- @HerlySQR, for showing a way to get a stack trace in Wc3 (https://www.hiveworkshop.com/threads/lua-getstacktrace.340841/)
- @Macadamia, for showing a way to print warnings upon accessing undeclared globals, where this all started with (https://www.hiveworkshop.com/threads/lua-very-simply-trick-to-help-lua-users-track-syntax-errors.326266/)
-----------------------------------------------------------------------------------------------------------------------------
| Provides debugging utility for Wc3-maps using Lua. |
| |
| Including: |
| 1. Automatic ingame error messages upon running erroneous code from triggers or timers. |
| 2. Ingame Console that allows you to execute code via Wc3 ingame chat. |
| 3. Automatic warnings upon reading undeclared globals (which also triggers after misspelling globals) |
| 4. Debug-Library functions for manual error handling. |
| 5. Caching of loading screen print messages until game start (which simplifies error handling during loading screen) |
| 6. Overwritten tostring/print-functions to show the actual string-name of an object instead of the memory position. |
| 7. Conversion of war3map.lua-error messages to local file error messages. |
| 8. Other useful debug utility (table.print and Debug.wc3Type) |
-----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Installation: |
| |
| 1. Copy the code (DebugUtils.lua, StringWidth.lua and IngameConsole.lua) into your map. Use script files (Ctrl+U) in your trigger editor, not text-based triggers! |
| 2. Order the files: DebugUtils above StringWidth above IngameConsole. Make sure they are above ALL other scripts (crucial for local line number feature). |
| 3. Adjust the settings in the settings-section further below to receive the debug environment that fits your needs. |
| |
| Deinstallation: |
| |
| - Debug Utils is meant to provide debugging utility and as such, shall be removed or invalidated from the map closely before release. |
| - Optimally delete the whole Debug library. If that isn't suitable (because you have used library functions at too many places), you can instead replace Debug Utils |
| by the following line of code that will invalidate all Debug functionality (without breaking your code): |
| Debug = setmetatable({try = function(...) return select(2,pcall(...)) end}, {__index = function(t,k) return DoNothing end}); try = Debug.try |
| - If that is also not suitable for you (because your systems rely on the Debug functionality to some degree), at least set ALLOW_INGAME_CODE_EXECUTION to false. |
| - Be sure to test your map thoroughly after removing Debug Utils. |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
* Documentation and API-Functions:
*
* - All automatic functionality provided by Debug Utils can be deactivated using the settings directly below the documentation.
*
* -------------------------
* | Ingame Code Execution |
* -------------------------
* - Debug Utils provides the ability to run code via chat command from within Wc3, if you have conducted step 3 from the installation section.
* - You can either open the ingame console by typing "-console" into the chat, or directly execute code by typing "-exec <code>".
* - See IngameConsole script for further documentation.
*
* ------------------
* | Error Handling |
* ------------------
* - Debug Utils automatically applies error handling (i.e. Debug.try) to code executed by your triggers and timers (error handling means that error messages are printed on screen, if anything doesn't run properly).
* - You can still use the below library functions for manual debugging.
*
* Debug.try(funcToExecute, ...) / try(funcToExecute, ...) -> ...
* - Calls the specified function with the specified parameters in protected mode (i.e. code after Debug.try will continue to run even if the function fails to execute).
* - If the call is successful, returns the specified function's original return values (so p1 = Debug.try(Player, 0) will work fine).
* - If the call is unsuccessful, prints an error message on screen (including stack trace and parameters you have potentially logged before the error occured)
* - By default, the error message consists of a line-reference to war3map.lua (which you can look into by forcing a syntax error in WE or by exporting it from your map via File -> Export Script).
* You can get more helpful references to local script files instead, see section about "Local script references".
* - Example: Assume you have a code line like "func(param1,param2)", which doesn't work and you want to know why.
* Option 1: Change it to "Debug.try(func, param1, param2)", i.e. separate the function from the parameters.
* Option 2: Change it to "Debug.try(function() return func(param1, param2) end)", i.e. pack it into an anonymous function (optionally skip the return statement).
* Debug.log(...)
* - Logs the specified parameters to the Debug-log. The Debug-log will be printed upon the next error being catched by Debug.try, Debug.assert or Debug.throwError.
* - The Debug-log will only hold one set of parameters per code-location. That means, if you call Debug.log() inside any function, only the params saved within the latest call of that function will be kept.
* Debug.throwError(...)
* - Prints an error message including document, line number, stack trace, previously logged parameters and all specified parameters on screen. Parameters can have any type.
* - In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
* Debug.assert(condition:boolean, errorMsg:string, ...) -> ...
* - Prints the specified error message including document, line number, stack trace and previously logged parameters on screen, IF the specified condition fails (i.e. resolves to false/nil).
* - Returns ..., IF the specified condition holds.
* - This works exactly like Lua's native assert, except that it also works outside of protected mode and does not halt code execution.
* Debug.traceback() -> string
* - Returns the stack trace at the position where this is called. You need to manually print it.
* Debug.getLine([depth: integer]) -> integer?
* - Returns the line in war3map.lua, where this function is executed.
* - You can specify a depth d >= 1 to instead return the line, where the d-th function in the stack trace was called. I.e. depth = 2 will return the line of execution of the function that calls Debug.getLine.
* - Due to Wc3's limited stack trace ability, this might sometimes return nil for depth >= 3, so better apply nil-checks on the result.
* Debug.getLocalErrorMsg(errorMsg:string) -> string
* - Takes an error message containing a file and a linenumber and converts war3map.lua-lines to local document lines as defined by uses of Debug.beginFile() and Debug.endFile().
* - Error Msg must be formatted like "<document>:<linenumber><Rest>".
*
* -----------------------------------
* | Warnings for undeclared globals |
* -----------------------------------
* - DebugUtils will print warnings on screen, if you read an undeclared global variable.
* - This is technically the case, when you misspelled on a function name, like calling CraeteUnit instead of CreateUnit.
* - Keep in mind though that the same warning will pop up after reading a global that was intentionally nilled. If you don't like this, turn of this feature in the settings.
*
* -----------------
* | Print Caching |
* -----------------
* - DebugUtils caches print()-calls occuring during loading screen and delays them to after game start.
* - This also applies to loading screen error messages, so you can wrap erroneous parts of your Lua root in Debug.try-blocks and see the message after game start.
*
* -------------------------
* | Local File Stacktrace |
* -------------------------
* - By default, error messages and stack traces printed by the error handling functionality of Debug Utils contain references to war3map.lua (a big file just appending all your local scripts).
* - The Debug-library provides the two functions below to index your local scripts, activating local file names and line numbers (matching those in your IDE) instead of the war3map.lua ones.
* - This allows you to inspect errors within your IDE (VSCode) instead of the World Editor.
*
* Debug.beginFile(fileName: string [, depth: integer])
* - Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
* - Using this improves stack traces of error messages. "war3map.lua"-references between <here> and the next Debug.endFile() will be converted to file-specific references.
* - All war3map.lua-lines located between the call of Debug.beginFile(fileName) and the next call of Debug.beginFile OR Debug.endFile are treated to be part of "fileName".
* - !!! To be called in the Lua root in Line 1 of every document you wish to track. Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
* - Depth can be ignored, except if you want to use a custom wrapper around Debug.beginFile(), in which case you need to set the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
* Debug.endFile([depth: integer])
* - Ends the current file that was previously begun by using Debug.beginFile(). War3map.lua-lines after this will not be converted until the next instance of Debug.beginFile().
* - The next call of Debug.beginFile() will also end the previous one, so using Debug.endFile() is optional. Mainly recommended to use, if you prefer to have war3map.lua-references in a certain part of your script (such as within GUI triggers).
* - Depth can be ignored, except if you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
*
* ----------------
* | Name Caching |
* ----------------
* - DebugUtils overwrites the tostring-function so that it prints the name of a non-primitive object (if available) instead of its memory position. The same applies to print().
* - For instance, print(CreateUnit) will show "function: CreateUnit" on screen instead of "function: 0063A698".
* - The table holding all those names is referred to as "Name Cache".
* - All names of objects in global scope will automatically be added to the Name Cache both within Lua root and again at game start (to get names for overwritten natives and your own objects).
* - New names entering global scope will also automatically be added, even after game start. The same applies to subtables of _G up to a depth of Debug.settings.NAME_CACHE_DEPTH.
* - Objects within subtables will be named after their parent tables and keys. For instance, the name of the function within T = {{bla = function() end}} is "T[1].bla".
* - The automatic adding doesn't work for objects saved into existing variables/keys after game start (because it's based on __newindex metamethod which simply doesn't trigger)
* - You can manually add names to the name cache by using the following API-functions:
*
* Debug.registerName(whichObject:any, name:string)
* - Adds the specified object under the specified name to the name cache, letting tostring and print output "<type>: <name>" going foward.
* - The object must be non-primitive, i.e. this won't work on strings, numbers and booleans.
* - This will overwrite existing names for the specified object with the specified name.
* Debug.registerNamesFrom(parentTable:table [, parentTableName:string] [, depth])
* - Adds names for all values from within the specified parentTable to the name cache.
* - Names for entries will be like "<parentTableName>.<key>" or "<parentTableName>[<key>]" (depending on the key type), using the existing name of the parentTable from the name cache.
* - You can optionally specify a parentTableName to use that for the entry naming instead of the existing name. Doing so will also register that name for the parentTable, if it doesn't already has one.
* - Specifying the empty string as parentTableName will suppress it in the naming and just register all values as "<key>". Note that only string keys will be considered this way.
* - In contrast to Debug.registerName(), this function will NOT overwrite existing names, but just add names for new objects.
* Debug.oldTostring(object:any) -> string
* - The old tostring-function in case you still need outputs like "function: 0063A698".
*
* -----------------
* | Other Utility |
* -----------------
*
* Debug.wc3Type(object:any) -> string
* - Returns the Warcraft3-type of the input object. E.g. Debug.wc3Type(Player(0)) will return "player".
* - Returns type(object), if used on Lua-objects.
* table.tostring(whichTable [, depth:integer] [, pretty_yn:boolean])
* - Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth (unlimited, if not specified).
* - E.g. for T = {"a", 5, {7}}, table.tostring(T) would output '{(1, "a"), (2, 5), (3, {(1, 7)})}' (if using concise style, i.e. pretty_yn being nil or false).
* - Not specifying a depth can potentially lead to a stack overflow for self-referential tables (e.g X = {}; X[1] = X). Choose a sensible depth to prevent this (in doubt start with 1 and test upwards).
* - Supports pretty style by setting pretty_yn to true. Pretty style is linebreak-separated, uses indentations and has other visual improvements. Use it on small tables only, because Wc3 can't show that many linebreaks at once.
* - All of the following is valid syntax: table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
* - table.tostring is not multiplayer-synced.
* table.print(whichTable [, depth:integer] [, pretty_yn:boolean])
* - Prints table.tostring(...).
*
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------]]
----------------
--| Settings |--
----------------
Debug = {
--BEGIN OF SETTINGS--
settings = {
SHOW_TRACE_ON_ERROR = true ---Set to true to show a stack trace on every error in addition to the regular message (msg sources: automatic error handling, Debug.try, Debug.throwError, ...)
, USE_TRY_ON_TRIGGERADDACTION = true ---Set to true for automatic error handling on TriggerAddAction (applies Debug.try on every trigger action).
, USE_TRY_ON_CONDITION = true ---Set to true for automatic error handling on boolexpressions created via Condition() or Filter() (essentially applies Debug.try on every trigger condition).
, USE_TRY_ON_TIMERSTART = true ---Set to true for automatic error handling on TimerStart (applies Debug.try on every timer callback).
, USE_TRY_ON_COROUTINES = true ---Set to true for improved stack traces on errors within coroutines (applies Debug.try on coroutine.create and coroutine.wrap). This lets stack traces point to the erroneous function executed within the coroutine (instead of the function creating the coroutine).
, ALLOW_INGAME_CODE_EXECUTION = true ---Set to true to enable IngameConsole and -exec command.
, WARNING_FOR_UNDECLARED_GLOBALS = true ---Set to true to print warnings upon accessing undeclared globals (i.e. globals with nil-value). This is technically the case after having misspelled on a function name (like CraeteUnit instead of CreateUnit).
, SHOW_TRACE_FOR_UNDECLARED_GLOBALS = true ---Set to true to include a stack trace into undeclared global warnings. Only takes effect, if WARNING_FOR_UNDECLARED_GLOBALS is also true.
, USE_PRINT_CACHE = true ---Set to true to let print()-calls during loading screen be cached until the game starts.
, PRINT_DURATION = nil ---Adjust the duration in seconds that values printed by print() last on screen. Set to nil to use default duration (which depends on string length).
, USE_NAME_CACHE = true ---Set to true to let tostring/print output the string-name of an object instead of its memory location (except for booleans/numbers/strings). E.g. print(CreateUnit) will output "function: CreateUnit" instead of "function: 0063A698".
, AUTO_REGISTER_NEW_NAMES = true ---Automatically adds new names from global scope (and subtables of _G up to NAME_CACHE_DEPTH) to the name cache by adding metatables with the __newindex metamethod to ALL tables accessible from global scope.
, NAME_CACHE_DEPTH = 0 ---Set to 0 to only affect globals. Experimental feature: Set to an integer > 0 to also cache names for subtables of _G (up to the specified depth). Warning: This will alter the __newindex metamethod of subtables of _G (but not break existing functionality).
}
--END OF SETTINGS--
--START OF CODE--
, data = {
nameCache = {} ---@type table<any,string> contains the string names of any object in global scope (random for objects that have multiple names)
, nameCacheMirror = {} ---@type table<string,any> contains the (name,object)-pairs of all objects in the name cache. Used to prevent name duplicates that might otherwise occur upon reassigning globals.
, nameDepths = {} ---@type table<any,integer> contains the depth of the name used by by any object in the name cache (i.e. the depth within the parentTable).
, autoIndexedTables = {} ---@type table<table,boolean> contains (t,true), if DebugUtils already set a __newindex metamethod for name caching in t. Prevents double application.
, paramLog = {} ---@type table<string,string> saves logged information per code location. to be filled by Debug.log(), to be printed by Debug.try()
, sourceMap = {{firstLine= 1,file='DebugUtils'}} ---@type table<integer,{firstLine:integer,file:string,lastLine?:integer}> saves lines and file names of all documents registered via Debug.beginFile().
, printCache = {n=0} ---@type string[] contains the strings that were attempted to print during loading screen.
}
}
--localization
local settings, paramLog, nameCache, nameDepths, autoIndexedTables, nameCacheMirror, sourceMap, printCache = Debug.settings, Debug.data.paramLog, Debug.data.nameCache, Debug.data.nameDepths, Debug.data.autoIndexedTables, Debug.data.nameCacheMirror, Debug.data.sourceMap, Debug.data.printCache
--Write DebugUtils first line number to sourceMap:
---@diagnostic disable-next-line: need-check-nil
Debug.data.sourceMap[1].firstLine = tonumber(codeLoc:match(":\x25d+"):sub(2,-1))
-------------------------------------------------
--| File Indexing for local Error Msg Support |--
-------------------------------------------------
-- Functions for war3map.lua -> local file conversion for error messages.
---Returns the line number in war3map.lua, where this is called (for depth = 0).
---Choose a depth > 0 to instead return the line, where the corresponding function in the stack leading to this call is executed.
---@param depth? integer default: 0.
---@return number?
function Debug.getLine(depth)
depth = depth or 0
local _, location = pcall(error, "", depth + 3) ---@diagnostic disable-next-line: need-check-nil
local line = location:match(":\x25d+") --extracts ":1000" from "war3map.lua:1000:..."
return tonumber(line and line:sub(2,-1)) --check if line is nil before applying string.sub to prevent errors (nil can result from string.match above, although it should never do so in our case)
end
---Tells the Debug library that the specified file begins exactly here (i.e. in the line, where this is called).
---
---Using this improves stack traces of error messages. Stack trace will have "war3map.lua"-references between this and the next Debug.endFile() converted to file-specific references.
---
---To be called in the Lua root in Line 1 of every file you wish to track! Line 1 means exactly line 1, before any comment! This way, the line shown in the trace will exactly match your IDE.
---
---If you want to use a custom wrapper around Debug.beginFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.beginFile().
---@param fileName string
---@param depth? integer default: 0. Set to 1, if you call this from a wrapper (and use the wrapper in line 1 of every document).
---@param lastLine? integer Ignore this. For compatibility with Total Initialization.
function Debug.beginFile(fileName, depth, lastLine)
depth, fileName = depth or 0, fileName or '' --filename is not actually optional, we just default to '' to prevent crashes.
local line = Debug.getLine(depth + 1)
if line then --for safety reasons. we don't want to add a non-existing line to the sourceMap
table.insert(sourceMap, {firstLine = line, file = fileName, lastLine = lastLine}) --automatically sorted list, because calls of Debug.beginFile happen logically in the order of the map script.
end
end
---Tells the Debug library that the file previously started with Debug.beginFile() ends here.
---This is in theory optional to use, as the next call of Debug.beginFile will also end the previous. Still good practice to always use this in the last line of every file.
---If you want to use a custom wrapper around Debug.endFile(), you need to increase the depth parameter to 1 to record the line of the wrapper instead of the line of Debug.endFile().
---@param depth? integer
function Debug.endFile(depth)
depth = depth or 0
local line = Debug.getLine(depth + 1)
sourceMap[#sourceMap].lastLine = line
end
---Takes an error message containing a file and a linenumber and converts both to local file and line as saved to Debug.sourceMap.
---@param errorMsg string must be formatted like "<document>:<linenumber><RestOfMsg>".
---@return string convertedMsg a string of the form "<localDocument>:<localLinenumber><RestOfMsg>"
function Debug.getLocalErrorMsg(errorMsg)
local startPos, endPos = errorMsg:find(":\x25d*") --start and end position of line number. The part before that is the document, part after the error msg.
if startPos and endPos then --can be nil, if input string was not of the desired form "<document>:<linenumber><RestOfMsg>".
local document, line, rest = errorMsg:sub(1, startPos), tonumber(errorMsg:sub(startPos+1, endPos)), errorMsg:sub(endPos+1, -1) --get error line in war3map.lua
if document == 'war3map.lua:' and line then --only convert war3map.lua-references to local position. Other files such as Blizzard.j.lua are not converted (obiously).
for i = #sourceMap, 1, -1 do --find local file containing the war3map.lua error line.
if line >= sourceMap[i].firstLine then --war3map.lua line is part of sourceMap[i].file
if not sourceMap[i].lastLine or line <= sourceMap[i].lastLine then --if lastLine is given, we must also check for it
return sourceMap[i].file .. ":" .. (line - sourceMap[i].firstLine + 1) .. rest
else --if line is larger than firstLine and lastLine of sourceMap[i], it is not part of a tracked file -> return global war3map.lua position.
break --prevent return within next step of the loop ("line >= sourceMap[i].firstLine" would be true again, but wrong file)
end
end
end
end
end
return errorMsg
end
local convertToLocalErrorMsg = Debug.getLocalErrorMsg
----------------------
--| Error Handling |--
----------------------
local concat
---Applies tostring() on all input params and concatenates them 4-space-separated.
---@param firstParam any
---@param ... any
---@return string
concat = function(firstParam, ...)
if select('#', ...) == 0 then
return tostring(firstParam)
end
return tostring(firstParam) .. ' ' .. concat(...)
end
---Returns the stack trace between the specified startDepth and endDepth.
---The trace lists file names and line numbers. File name is only listed, if it has changed from the previous traced line.
---The previous file can also be specified as an input parameter to suppress the first file name in case it's identical.
---@param startDepth integer
---@param endDepth integer
---@return string trace
local function getStackTrace(startDepth, endDepth)
local trace, separator = "", ""
local _, lastFile, tracePiece, lastTracePiece
for loopDepth = startDepth, endDepth do --get trace on different depth level
_, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
tracePiece = convertToLocalErrorMsg(tracePiece)
if #tracePiece > 0 and lastTracePiece ~= tracePiece then --some trace pieces can be empty, but there can still be valid ones beyond that
trace = trace .. separator .. ((tracePiece:match("^.-:") == lastFile) and tracePiece:match(":\x25d+"):sub(2,-1) or tracePiece:match("^.-:\x25d+"))
lastFile, lastTracePiece, separator = tracePiece:match("^.-:"), tracePiece, " <- "
end
end
return trace
end
---Message Handler to be used by the try-function below.
---Adds stack trace plus formatting to the message and prints it.
---@param errorMsg string
---@param startDepth? integer default: 4 for use in xpcall
local function errorHandler(errorMsg, startDepth)
startDepth = startDepth or 4 --xpcall doesn't specify this param, so it defaults to 4 in this case
errorMsg = convertToLocalErrorMsg(errorMsg)
--Print original error message and stack trace.
print("|cffff5555ERROR at " .. errorMsg .. "|r")
if settings.SHOW_TRACE_ON_ERROR then
print("|cffff5555Traceback (most recent call first):|r")
print("|cffff5555" .. getStackTrace(startDepth,200) .. "|r")
end
--Also print entries from param log, if there are any.
for location, loggedParams in pairs(paramLog) do
print("|cff888888Logged at " .. convertToLocalErrorMsg(location) .. loggedParams .. "|r")
paramLog[location] = nil
end
end
---Tries to execute the specified function with the specified parameters in protected mode and prints an error message (including stack trace), if unsuccessful.
---
---Example use: Assume you have a code line like "CreateUnit(0,1,2)", which doesn't work and you want to know why.
---* Option 1: Change it to "Debug.try(CreateUnit, 0, 1, 2)", i.e. separate the function from the parameters.
---* Option 2: Change it to "Debug.try(function() return CreateUnit(0,1,2) end)", i.e. pack it into an anonymous function. You can skip the "return", if you don't need the return values.
---When no error occured, the try-function will return all values returned by the input function.
---When an error occurs, try will print the resulting error and stack trace.
---@param funcToExecute function the function to call in protected mode
---@param ... any params for the input-function
---@return ... any
function Debug.try(funcToExecute, ...)
return select(2, xpcall(funcToExecute, errorHandler,...))
end
try = Debug.try
---Prints "ERROR:" and the specified error objects on the Screen. Also prints the stack trace leading to the error. You can specify as many arguments as you wish.
---
---In contrast to Lua's native error function, this can be called outside of protected mode and doesn't halt code execution.
---@param ... any objects/errormessages to be printed (doesn't have to be strings)
function Debug.throwError(...)
errorHandler(getStackTrace(4,4) .. ": " .. concat(...), 5)
end
---Prints the specified error message, if the specified condition fails (i.e. if it resolves to false or nil).
---
---Returns all specified arguments after the errorMsg, if the condition holds.
---
---In contrast to Lua's native assert function, this can be called outside of protected mode and doesn't halt code execution (even in case of condition failure).
---@param condition any actually a boolean, but you can use any object as a boolean.
---@param errorMsg string the message to be printed, if the condition fails
---@param ... any will be returned, if the condition holds
function Debug.assert(condition, errorMsg, ...)
if condition then
return ...
else
errorHandler(getStackTrace(4,4) .. ": " .. errorMsg, 5)
end
end
---Returns the stack trace at the code position where this function is called.
---The returned string includes war3map.lua/blizzard.j.lua code positions of all functions from the stack trace in the order of execution (most recent call last). It does NOT include function names.
---@return string
function Debug.traceback()
return getStackTrace(3,200)
end
---Saves the specified parameters to the debug log at the location where this function is called. The Debug-log will be printed for all affected locations upon the try-function catching an error.
---The log is unique per code location: Parameters logged at code line x will overwrite the previous ones logged at x. Parameters logged at different locations will all persist and be printed.
---@param ... any save any information, for instance the parameters of the function call that you are logging.
function Debug.log(...)
local _, location = pcall(error, "", 3) ---@diagnostic disable-next-line: need-check-nil
paramLog[location or ''] = concat(...)
end
------------------------------------
--| Name Caching (API-functions) |--
------------------------------------
--Help-table. The registerName-functions below shall not work on call-by-value-types, i.e. booleans, strings and numbers (renaming a value of any primitive type doesn't make sense).
local skipType = {boolean = true, string = true, number = true, ['nil'] = true}
--Set weak keys to nameCache and nameDepths and weak values for nameCacheMirror to prevent garbage collection issues
setmetatable(nameCache, {__mode = 'k'})
setmetatable(nameDepths, getmetatable(nameCache))
setmetatable(nameCacheMirror, {__mode = 'v'})
---Removes the name from the name cache, if already used for any object (freeing it for the new object). This makes sure that a name is always unique.
---This doesn't solve the
---@param name string
local function removeNameIfNecessary(name)
if nameCacheMirror[name] then
nameCache[nameCacheMirror[name]] = nil
nameCacheMirror[name] = nil
end
end
---Registers a name for the specified object, which will be the future output for tostring(whichObject).
---You can overwrite existing names for whichObject by using this.
---@param whichObject any
---@param name string
function Debug.registerName(whichObject, name)
if not skipType[type(whichObject)] then
removeNameIfNecessary(name)
nameCache[whichObject] = name
nameCacheMirror[name] = whichObject
nameDepths[name] = 0
end
end
---Registers a new name to the nameCache as either just <key> (if parentTableName is the empty string), <table>.<key> (if parentTableName is given and string key doesn't contain whitespace) or <name>[<key>] notation (for other keys in existing tables).
---Only string keys without whitespace support <key>- and <table>.<key>-notation. All other keys require a parentTableName.
---@param parentTableName string | '""' empty string suppresses <table>-affix.
---@param key any
---@param object any only call-be-ref types allowed
---@param parentTableDepth? integer
local function addNameToCache(parentTableName, key, object, parentTableDepth)
parentTableDepth = parentTableDepth or -1
--Don't overwrite existing names for the same object, don't add names for primitive types.
if nameCache[object] or skipType[type(object)] then
return
end
local name
--apply dot-syntax for string keys without whitespace
if type(key) == 'string' and not string.find(key, "\x25s") then
if parentTableName == "" then
name = key
nameDepths[object] = 0
else
name = parentTableName .. "." .. key
nameDepths[object] = parentTableDepth + 1
end
--apply bracket-syntax for all other keys. This requires a parentTableName.
elseif parentTableName ~= "" then
name = type(key) == 'string' and ('"' .. key .. '"') or key
name = parentTableName .. "[" .. tostring(name) .. "]"
nameDepths[object] = parentTableDepth + 1
end
--Stop in cases without valid name (like parentTableName = "" and key = [1])
if name then
removeNameIfNecessary(name)
nameCache[object] = name
nameCacheMirror[name] = object
end
end
---Registers all call-by-reference objects in the given parentTable to the nameCache.
---Automatically filters out primitive objects and already registed Objects.
---@param parentTable table
---@param parentTableName? string
local function registerAllObjectsInTable(parentTable, parentTableName)
parentTableName = parentTableName or nameCache[parentTable] or ""
--Register all call-by-ref-objects in parentTable
for key, object in pairs(parentTable) do
addNameToCache(parentTableName, key, object, nameDepths[parentTable])
end
end
---Adds names for all values of the specified parentTable to the name cache. Names will be "<parentTableName>.<key>" or "<parentTableName>[<key>]", depending on the key type.
---
---Example: Given a table T = {f = function() end, [1] = {}}, tostring(T.f) and tostring(T[1]) will output "function: T.f" and "table: T[1]" respectively after running Debug.registerNamesFrom(T).
---The name of T itself must either be specified as an input parameter OR have previously been registered. It can also be suppressed by inputting the empty string (so objects will just display by their own names).
---The names of objects in global scope are automatically registered during loading screen.
---@param parentTable table base table of which all entries shall be registered (in the Form parentTableName.objectName).
---@param parentTableName? string|'""' Nil: takes <parentTableName> as previously registered. Empty String: Skips <parentTableName> completely. String <s>: Objects will show up as "<s>.<objectName>".
---@param depth? integer objects within sub-tables up to the specified depth will also be added. Default: 1 (only elements of whichTable). Must be >= 1.
---@overload fun(parentTable:table, depth:integer)
function Debug.registerNamesFrom(parentTable, parentTableName, depth)
--Support overloaded definition fun(parentTable:table, depth:integer)
if type(parentTableName) == 'number' then
depth = parentTableName
parentTableName = nil
end
--Apply default values
depth = depth or 1
parentTableName = parentTableName or nameCache[parentTable] or ""
--add name of T in case it hasn't already
if not nameCache[parentTable] and parentTableName ~= "" then
Debug.registerName(parentTable, parentTableName)
end
--Register all call-by-ref-objects in parentTable. To be preferred over simple recursive approach to ensure that top level names are preferred.
registerAllObjectsInTable(parentTable, parentTableName)
--if depth > 1 was specified, also register Names from subtables.
if depth > 1 then
for _, object in pairs(parentTable) do
if type(object) == 'table' then
Debug.registerNamesFrom(object, nil, depth - 1)
end
end
end
end
-------------------------------------------
--| Name Caching (Loading Screen setup) |--
-------------------------------------------
---Registers all existing object names from global scope and Lua incorporated libraries to be used by tostring() overwrite below.
local function registerNamesFromGlobalScope()
--Add all names from global scope to the name cache.
Debug.registerNamesFrom(_G, "")
--Add all names of Warcraft-enabled Lua libraries as well:
--Could instead add a depth to the function call above, but we want to ensure that these libraries are added even if the user has chosen depth 0.
for _, lib in ipairs({coroutine, math, os, string, table, utf8, Debug}) do
Debug.registerNamesFrom(lib)
end
--Add further names that are not accessible from global scope:
--Player(i)
for i = 0, GetBJMaxPlayerSlots() - 1 do
Debug.registerName(Player(i), "Player(" .. i .. ")")
end
end
--Set empty metatable to _G. __index is added when game starts (for "attempt to read undeclared global"-errors), __newindex is added right below (for building the name cache).
setmetatable(_G, getmetatable(_G) or {}) --getmetatable(_G) should always return nil provided that DebugUtils is the topmost script file in the trigger editor, but we still include this for safety-
-- Save old tostring into Debug Library before overwriting it.
Debug.oldTostring = tostring
if settings.USE_NAME_CACHE then
local oldTostring = tostring
tostring = function(obj) --new tostring(CreateUnit) prints "function: CreateUnit"
--tostring of non-primitive object is NOT guaranteed to be like "<type>:<hex>", because it might have been changed by some __tostring-metamethod.
if settings.USE_NAME_CACHE then --return names from name cache only if setting is enabled. This allows turning it off during runtime (via Ingame Console) to revert to old tostring.
return nameCache[obj] and ((oldTostring(obj):match("^.-: ") or (oldTostring(obj) .. ": ")) .. nameCache[obj]) or oldTostring(obj)
end
return Debug.oldTostring(obj)
end
--Add names to Debug.data.objectNames within Lua root. Called below the other Debug-stuff to get the overwritten versions instead of the original ones.
registerNamesFromGlobalScope()
--Prepare __newindex-metamethod to automatically add new names to the name cache
if settings.AUTO_REGISTER_NEW_NAMES then
local nameRegisterNewIndex
---__newindex to be used for _G (and subtables up to a certain depth) to automatically register new names to the nameCache.
---Tables in global scope will use their own name. Subtables of them will use <parentName>.<childName> syntax.
---Global names don't support container[key]-notation (because "_G[...]" is probably not desired), so we only register string type keys instead of using prettyTostring.
---@param t table
---@param k any
---@param v any
---@param skipRawset? boolean set this to true when combined with another __newindex. Suppresses rawset(t,k,v) (because the other __newindex is responsible for that).
nameRegisterNewIndex = function(t,k,v, skipRawset)
local parentDepth = nameDepths[t] or 0
--Make sure the parent table has an existing name before using it as part of the child name
if t == _G or nameCache[t] then
local existingName = nameCache[v]
if not existingName then
addNameToCache((t == _G and "") or nameCache[t], k, v, parentDepth)
end
--If v is a table and the parent table has a valid name, inherit __newindex to v's existing metatable (or create a new one), if that wasn't already done.
if type(v) == 'table' and nameDepths[v] < settings.NAME_CACHE_DEPTH then
if not existingName then
--If v didn't have a name before, also add names for elements contained in v by construction (like v = {x = function() end} ).
Debug.registerNamesFrom(v, settings.NAME_CACHE_DEPTH - nameDepths[v])
end
--Apply __newindex to new tables.
if not autoIndexedTables[v] then
autoIndexedTables[v] = true
local mt = getmetatable(v)
if not mt then
mt = {}
setmetatable(v, mt) --only use setmetatable when we are sure there wasn't any before to prevent issues with "__metatable"-metamethod.
end
local existingNewIndex = mt.__newindex
local isTable_yn = (type(existingNewIndex) == 'table')
--If mt has an existing __newindex, add the name-register effect to it (effectively create a new __newindex using the old)
if existingNewIndex then
mt.__newindex = function(t,k,v)
nameRegisterNewIndex(t,k,v, true) --setting t[k] = v might not be desired in case of existing newindex. Skip it and let existingNewIndex make the decision.
if isTable_yn then
existingNewIndex[k] = v
else
return existingNewIndex(t,k,v)
end
end
else
--If mt doesn't have an existing __newindex, add one that adds the object to the name cache.
mt.__newindex = nameRegisterNewIndex
end
end
end
end
--Set t[k] = v.
if not skipRawset then
rawset(t,k,v)
end
end
--Apply metamethod to _G.
local existingNewIndex = getmetatable(_G).__newindex --should always be nil provided that DebugUtils is the topmost script in your trigger editor. Still included for safety.
local isTable_yn = (type(existingNewIndex) == 'table')
if existingNewIndex then
getmetatable(_G).__newindex = function(t,k,v)
nameRegisterNewIndex(t,k,v, true)
if isTable_yn then
existingNewIndex[k] = v
else
existingNewIndex(t,k,v)
end
end
else
getmetatable(_G).__newindex = nameRegisterNewIndex
end
end
end
------------------------------------------------------
--| Native Overwrite for Automatic Error Handling |--
------------------------------------------------------
--A table to store the try-wrapper for each function. This avoids endless re-creation of wrapper functions within the hooks below.
--Weak keys ensure that garbage collection continues as normal.
local tryWrappers = setmetatable({}, {__mode = 'k'}) ---@type table<function,function>
local try = Debug.try
---Takes a function and returns a wrapper executing the same function within Debug.try.
---Wrappers are permanently stored (until the original function is garbage collected) to ensure that they don't have to be created twice for the same function.
---@param func? function
---@return function
local function getTryWrapper(func)
if func then
tryWrappers[func] = tryWrappers[func] or function(...) return try(func, ...) end
end
return tryWrappers[func] --returns nil for func = nil (important for TimerStart overwrite below)
end
--Overwrite TriggerAddAction, TimerStart, Condition and Filter natives to let them automatically apply Debug.try.
--Also overwrites coroutine.create and coroutine.wrap to let stack traces point to the function executed within instead of the function creating the coroutine.
if settings.USE_TRY_ON_TRIGGERADDACTION then
local originalTriggerAddAction = TriggerAddAction
TriggerAddAction = function(whichTrigger, actionFunc)
return originalTriggerAddAction(whichTrigger, getTryWrapper(actionFunc))
end
end
if settings.USE_TRY_ON_TIMERSTART then
local originalTimerStart = TimerStart
TimerStart = function(whichTimer, timeout, periodic, handlerFunc)
originalTimerStart(whichTimer, timeout, periodic, getTryWrapper(handlerFunc))
end
end
if settings.USE_TRY_ON_CONDITION then
local originalCondition = Condition
Condition = function(func)
return originalCondition(getTryWrapper(func))
end
Filter = Condition
end
if settings.USE_TRY_ON_COROUTINES then
local originalCoroutineCreate = coroutine.create
---@diagnostic disable-next-line: duplicate-set-field
coroutine.create = function(f)
return originalCoroutineCreate(getTryWrapper(f))
end
local originalCoroutineWrap = coroutine.wrap
---@diagnostic disable-next-line: duplicate-set-field
coroutine.wrap = function(f)
return originalCoroutineWrap(getTryWrapper(f))
end
end
------------------------------------------
--| Cache prints during Loading Screen |--
------------------------------------------
-- Apply the duration as specified in the settings.
if settings.PRINT_DURATION then
local display, getLocalPlayer, dur = DisplayTimedTextToPlayer, GetLocalPlayer, settings.PRINT_DURATION
print = function(...)
display(getLocalPlayer(), 0, 0, dur, concat(...))
end
end
-- Delay loading screen prints to after game start.
if settings.USE_PRINT_CACHE then
local oldPrint = print
--loading screen print will write the values into the printCache
print = function(...)
if bj_gameStarted then
oldPrint(...)
else --during loading screen only: concatenate input arguments 4-space-separated, implicitely apply tostring on each, cache to table
printCache.n = printCache.n + 1
printCache[printCache.n] = concat(...)
end
end
end
-------------------------
--| Modify Game Start |--
-------------------------
local originalMarkGameStarted = MarkGameStarted
--Hook certain actions into the start of the game.
MarkGameStarted = function()
originalMarkGameStarted()
if settings.WARNING_FOR_UNDECLARED_GLOBALS then
local existingIndex = getmetatable(_G).__index
local isTable_yn = (type(existingIndex) == 'table')
getmetatable(_G).__index = function(t, k) --we made sure that _G has a metatable further above.
--if string.sub(tostring(k),1,3) ~= 'bj_' then
print("Trying to read undeclared global at " .. getStackTrace(4,4) .. ": " .. tostring(k)
.. (settings.SHOW_TRACE_FOR_UNDECLARED_GLOBALS and "\nTraceback (most recent call first):\n" .. getStackTrace(4,200) or ""))
--end
if existingIndex then
if isTable_yn then
return existingIndex[k]
end
return existingIndex(t,k)
end
return rawget(t,k)
end
end
--Add names to Debug.data.objectNames again to ensure that overwritten natives also make it to the name cache.
--Overwritten natives have a new value, but the old key, so __newindex didn't trigger. But we can be sure that objectNames[v] doesn't yet exist, so adding again is safe.
if settings.USE_NAME_CACHE then
for _,v in pairs(_G) do
nameCache[v] = nil
end
registerNamesFromGlobalScope()
end
--Print messages that have been cached during loading screen.
if settings.USE_PRINT_CACHE then
--Note that we don't restore the old print. The overwritten variant only applies caching behaviour to loading screen prints anyway and "unhooking" always adds other risks.
for _, str in ipairs(printCache) do
print(str)
end
printCache = nil --frees reference for the garbage collector
end
--Create triggers listening to "-console" and "-exec" chat input.
if settings.ALLOW_INGAME_CODE_EXECUTION and IngameConsole then
IngameConsole.createTriggers()
end
end
---------------------
--| Other Utility |--
---------------------
do
---Returns the type of a warcraft object as string, e.g. "unit" upon inputting a unit.
---@param input any
---@return string
function Debug.wc3Type(input)
local typeString = type(input)
if typeString == 'userdata' then
typeString = tostring(input) --tostring returns the warcraft type plus a colon and some hashstuff.
return typeString:sub(1, (typeString:find(":", nil, true) or 0) -1) --string.find returns nil, if the argument is not found, which would break string.sub. So we need to replace by 0.
else
return typeString
end
end
Wc3Type = Debug.wc3Type --for backwards compatibility
local conciseTostring, prettyTostring
---Translates a table into a comma-separated list of its (key,value)-pairs. Also translates subtables up to the specified depth.
---E.g. {"a", 5, {7}} will display as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
---@param object any
---@param depth? integer default: unlimited. Unlimited depth will throw a stack overflow error on self-referential tables.
---@return string
conciseTostring = function (object, depth)
depth = depth or -1
if type(object) == 'string' then
return '"' .. object .. '"'
elseif depth ~= 0 and type(object) == 'table' then
local elementArray = {}
local keyAsString
for k,v in pairs(object) do
keyAsString = type(k) == 'string' and ('"' .. tostring(k) .. '"') or tostring(k)
table.insert(elementArray, '(' .. keyAsString .. ', ' .. conciseTostring(v, depth -1) .. ')')
end
return '{' .. table.concat(elementArray, ', ') .. '}'
end
return tostring(object)
end
---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
---Major differences to concise print are:
--- * Format: Linebreak-formatted instead of one-liner, uses "[key] = value" instead of "(key,value)"
--- * Will also unpack tables used as keys
--- * Also includes the table's memory position as returned by tostring(table).
--- * Tables referenced multiple times will only be unpacked upon first encounter and abbreviated on subsequent encounters
--- * As a consequence, pretty version can be executed with unlimited depth on self-referential tables.
---@param object any
---@param depth? integer default: unlimited.
---@param constTable table
---@param indent string
---@return string
prettyTostring = function(object, depth, constTable, indent)
depth = depth or -1
local objType = type(object)
if objType == "string" then
return '"'..object..'"' --wrap the string in quotes.
elseif objType == 'table' and depth ~= 0 then
if not constTable[object] then
constTable[object] = tostring(object):gsub(":","")
if next(object)==nil then
return constTable[object]..": {}"
else
local mappedKV = {}
for k,v in pairs(object) do
table.insert(mappedKV, '\n ' .. indent ..'[' .. prettyTostring(k, depth - 1, constTable, indent .. " ") .. '] = ' .. prettyTostring(v, depth - 1, constTable, indent .. " "))
end
return constTable[object]..': {'.. table.concat(mappedKV, ',') .. '\n'..indent..'}'
end
end
end
return constTable[object] or tostring(object)
end
---Creates a list of all (key,value)-pairs from the specified table. Also lists subtable entries up to the specified depth.
---Supports concise style and pretty style.
---Concise will display {"a", 5, {7}} as '{(1, "a"), (2, 5), (3, {(1, 7)})}'.
---Pretty is linebreak-separated, so consider table size before converting. Pretty also abbreviates tables referenced multiple times.
---Can be called like table.tostring(T), table.tostring(T, depth), table.tostring(T, pretty_yn) or table.tostring(T, depth, pretty_yn).
---table.tostring is not multiplayer-synced.
---@param whichTable table
---@param depth? integer default: unlimited
---@param pretty_yn? boolean default: false (concise)
---@return string
---@overload fun(whichTable:table, pretty_yn?:boolean):string
function table.tostring(whichTable, depth, pretty_yn)
--reassign input params, if function was called as table.tostring(whichTable, pretty_yn)
if type(depth) == 'boolean' then
pretty_yn = depth
depth = -1
end
return pretty_yn and prettyTostring(whichTable, depth, {}, "") or conciseTostring(whichTable, depth)
end
---Prints a list of (key,value)-pairs contained in the specified table and its subtables up to the specified depth.
---Supports concise style and pretty style. Pretty is linebreak-separated, so consider table size before printing.
---Can be called like table.print(T), table.print(T, depth), table.print(T, pretty_yn) or table.print(T, depth, pretty_yn).
---@param whichTable table
---@param depth? integer default: unlimited
---@param pretty_yn? boolean default: false (concise)
---@overload fun(whichTable:table, pretty_yn?:boolean)
function table.print(whichTable, depth, pretty_yn)
print(table.tostring(whichTable, depth, pretty_yn))
end
end
end
Debug.endFile()
------------------------
----| String Width |----
------------------------
--[[
offers functions to measure the width of a string (i.e. the space it takes on screen, not the number of chars). Wc3 font is not monospace, so the system below has protocolled every char width and simply sums up all chars in a string.
output measures are:
1. Multiboard-width (i.e. 1-based screen share used in Multiboards column functions)
2. Line-width for screen prints
every unknown char will be treated as having default width (see constants below)
--]]
do
----------------------------
----| String Width API |----
----------------------------
local multiboardCharTable = {} ---@type table -- saves the width in screen percent (on 1920 pixel width resolutions) that each char takes up, when displayed in a multiboard.
local DEFAULT_MULTIBOARD_CHAR_WIDTH = 1. / 128. ---@type number -- used for unknown chars (where we didn't define a width in the char table)
local MULTIBOARD_TO_PRINT_FACTOR = 1. / 36. ---@type number -- 36 is actually the lower border (longest width of a non-breaking string only consisting of the letter "i")
---Returns the width of a char in a multiboard, when inputting a char (string of length 1) and 0 otherwise.
---also returns 0 for non-recorded chars (like ` and ´ and ß and § and €)
---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.charMultiboardWidth(char, textlanguage)
return multiboardCharTable[textlanguage or 'eng'][char] or DEFAULT_MULTIBOARD_CHAR_WIDTH
end
---returns the width of a string in a multiboard (i.e. output is in screen percent)
---unknown chars will be measured with default width (see constants above)
---@param multichar string
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.multiboardWidth(multichar, textlanguage)
local chartable = table.pack(multichar:byte(1, -1)) --packs all bytecode char representations into a table
local charWidth = 0.
for i = 1, chartable.n do
charWidth = charWidth + string.charMultiboardWidth(chartable[i], textlanguage)
end
return charWidth
end
---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
---@param char string | integer integer bytecode representations of chars are also allowed, i.e. the results of string.byte().
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.charPrintWidth(char, textlanguage)
return string.charMultiboardWidth(char, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
end
---The function should match the following criteria: If the value returned by this function is smaller than 1.0, than the string fits into a single line on screen.
---The opposite is not necessarily true (but should be true in the majority of cases): If the function returns bigger than 1.0, the string doesn't necessarily break.
---@param multichar string
---@param textlanguage? '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@return number
function string.printWidth(multichar, textlanguage)
return string.multiboardWidth(multichar, textlanguage) * MULTIBOARD_TO_PRINT_FACTOR
end
----------------------------------
----| String Width Internals |----
----------------------------------
---@param charset '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@param char string|integer either the char or its bytecode
---@param lengthInScreenWidth number
local function setMultiboardCharWidth(charset, char, lengthInScreenWidth)
multiboardCharTable[charset] = multiboardCharTable[charset] or {}
multiboardCharTable[charset][char] = lengthInScreenWidth
end
---numberPlacements says how often the char can be placed in a multiboard column, before reaching into the right bound.
---@param charset '"ger"'| '"eng"' (default: 'eng'), depending on the text language in the Warcraft 3 installation settings.
---@param char string|integer either the char or its bytecode
---@param numberPlacements integer
local function setMultiboardCharWidthBase80(charset, char, numberPlacements)
setMultiboardCharWidth(charset, char, 0.8 / numberPlacements) --1-based measure. 80./numberPlacements would result in Screen Percent.
setMultiboardCharWidth(charset, char:byte(1, -1), 0.8 / numberPlacements)
end
-- Set Char Width for all printable ascii chars in screen width (1920 pixels). Measured on a 80percent screen width multiboard column by counting the number of chars that fit into it.
-- Font size differs by text install language and patch (1.32- vs. 1.33+)
if BlzGetUnitOrderCount then --identifies patch 1.33+
--German font size for patch 1.33+
setMultiboardCharWidthBase80('ger', "a", 144)
setMultiboardCharWidthBase80('ger', "b", 131)
setMultiboardCharWidthBase80('ger', "c", 144)
setMultiboardCharWidthBase80('ger', "d", 120)
setMultiboardCharWidthBase80('ger', "e", 131)
setMultiboardCharWidthBase80('ger', "f", 240)
setMultiboardCharWidthBase80('ger', "g", 120)
setMultiboardCharWidthBase80('ger', "h", 131)
setMultiboardCharWidthBase80('ger', "i", 288)
setMultiboardCharWidthBase80('ger', "j", 288)
setMultiboardCharWidthBase80('ger', "k", 144)
setMultiboardCharWidthBase80('ger', "l", 288)
setMultiboardCharWidthBase80('ger', "m", 85)
setMultiboardCharWidthBase80('ger', "n", 131)
setMultiboardCharWidthBase80('ger', "o", 120)
setMultiboardCharWidthBase80('ger', "p", 120)
setMultiboardCharWidthBase80('ger', "q", 120)
setMultiboardCharWidthBase80('ger', "r", 206)
setMultiboardCharWidthBase80('ger', "s", 160)
setMultiboardCharWidthBase80('ger', "t", 206)
setMultiboardCharWidthBase80('ger', "u", 131)
setMultiboardCharWidthBase80('ger', "v", 131)
setMultiboardCharWidthBase80('ger', "w", 96)
setMultiboardCharWidthBase80('ger', "x", 144)
setMultiboardCharWidthBase80('ger', "y", 131)
setMultiboardCharWidthBase80('ger', "z", 144)
setMultiboardCharWidthBase80('ger', "A", 103)
setMultiboardCharWidthBase80('ger', "B", 120)
setMultiboardCharWidthBase80('ger', "C", 111)
setMultiboardCharWidthBase80('ger', "D", 103)
setMultiboardCharWidthBase80('ger', "E", 144)
setMultiboardCharWidthBase80('ger', "F", 160)
setMultiboardCharWidthBase80('ger', "G", 96)
setMultiboardCharWidthBase80('ger', "H", 96)
setMultiboardCharWidthBase80('ger', "I", 240)
setMultiboardCharWidthBase80('ger', "J", 240)
setMultiboardCharWidthBase80('ger', "K", 120)
setMultiboardCharWidthBase80('ger', "L", 144)
setMultiboardCharWidthBase80('ger', "M", 76)
setMultiboardCharWidthBase80('ger', "N", 96)
setMultiboardCharWidthBase80('ger', "O", 90)
setMultiboardCharWidthBase80('ger', "P", 131)
setMultiboardCharWidthBase80('ger', "Q", 90)
setMultiboardCharWidthBase80('ger', "R", 120)
setMultiboardCharWidthBase80('ger', "S", 131)
setMultiboardCharWidthBase80('ger', "T", 144)
setMultiboardCharWidthBase80('ger', "U", 103)
setMultiboardCharWidthBase80('ger', "V", 120)
setMultiboardCharWidthBase80('ger', "W", 76)
setMultiboardCharWidthBase80('ger', "X", 111)
setMultiboardCharWidthBase80('ger', "Y", 120)
setMultiboardCharWidthBase80('ger', "Z", 120)
setMultiboardCharWidthBase80('ger', "1", 144)
setMultiboardCharWidthBase80('ger', "2", 120)
setMultiboardCharWidthBase80('ger', "3", 120)
setMultiboardCharWidthBase80('ger', "4", 120)
setMultiboardCharWidthBase80('ger', "5", 120)
setMultiboardCharWidthBase80('ger', "6", 120)
setMultiboardCharWidthBase80('ger', "7", 131)
setMultiboardCharWidthBase80('ger', "8", 120)
setMultiboardCharWidthBase80('ger', "9", 120)
setMultiboardCharWidthBase80('ger', "0", 120)
setMultiboardCharWidthBase80('ger', ":", 288)
setMultiboardCharWidthBase80('ger', ";", 288)
setMultiboardCharWidthBase80('ger', ".", 288)
setMultiboardCharWidthBase80('ger', "#", 120)
setMultiboardCharWidthBase80('ger', ",", 288)
setMultiboardCharWidthBase80('ger', " ", 286) --space
setMultiboardCharWidthBase80('ger', "'", 180)
setMultiboardCharWidthBase80('ger', "!", 180)
setMultiboardCharWidthBase80('ger', "$", 131)
setMultiboardCharWidthBase80('ger', "&", 90)
setMultiboardCharWidthBase80('ger', "/", 180)
setMultiboardCharWidthBase80('ger', "(", 240)
setMultiboardCharWidthBase80('ger', ")", 240)
setMultiboardCharWidthBase80('ger', "=", 120)
setMultiboardCharWidthBase80('ger', "?", 144)
setMultiboardCharWidthBase80('ger', "^", 144)
setMultiboardCharWidthBase80('ger', "<", 144)
setMultiboardCharWidthBase80('ger', ">", 144)
setMultiboardCharWidthBase80('ger', "-", 180)
setMultiboardCharWidthBase80('ger', "+", 120)
setMultiboardCharWidthBase80('ger', "*", 180)
setMultiboardCharWidthBase80('ger', "|", 287) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('ger', "~", 111)
setMultiboardCharWidthBase80('ger', "{", 240)
setMultiboardCharWidthBase80('ger', "}", 240)
setMultiboardCharWidthBase80('ger', "[", 240)
setMultiboardCharWidthBase80('ger', "]", 240)
setMultiboardCharWidthBase80('ger', "_", 144)
setMultiboardCharWidthBase80('ger', "\x25", 103) --percent
setMultiboardCharWidthBase80('ger', "\x5C", 205) --backslash
setMultiboardCharWidthBase80('ger', "\x22", 120) --double quotation mark
setMultiboardCharWidthBase80('ger', "\x40", 90) --at sign
setMultiboardCharWidthBase80('ger', "\x60", 144) --Gravis (Accent)
--English font size for patch 1.33+
setMultiboardCharWidthBase80('eng', "a", 144)
setMultiboardCharWidthBase80('eng', "b", 120)
setMultiboardCharWidthBase80('eng', "c", 131)
setMultiboardCharWidthBase80('eng', "d", 120)
setMultiboardCharWidthBase80('eng', "e", 120)
setMultiboardCharWidthBase80('eng', "f", 240)
setMultiboardCharWidthBase80('eng', "g", 120)
setMultiboardCharWidthBase80('eng', "h", 120)
setMultiboardCharWidthBase80('eng', "i", 288)
setMultiboardCharWidthBase80('eng', "j", 288)
setMultiboardCharWidthBase80('eng', "k", 144)
setMultiboardCharWidthBase80('eng', "l", 288)
setMultiboardCharWidthBase80('eng', "m", 80)
setMultiboardCharWidthBase80('eng', "n", 120)
setMultiboardCharWidthBase80('eng', "o", 111)
setMultiboardCharWidthBase80('eng', "p", 111)
setMultiboardCharWidthBase80('eng', "q", 111)
setMultiboardCharWidthBase80('eng', "r", 206)
setMultiboardCharWidthBase80('eng', "s", 160)
setMultiboardCharWidthBase80('eng', "t", 206)
setMultiboardCharWidthBase80('eng', "u", 120)
setMultiboardCharWidthBase80('eng', "v", 144)
setMultiboardCharWidthBase80('eng', "w", 90)
setMultiboardCharWidthBase80('eng', "x", 131)
setMultiboardCharWidthBase80('eng', "y", 144)
setMultiboardCharWidthBase80('eng', "z", 144)
setMultiboardCharWidthBase80('eng', "A", 103)
setMultiboardCharWidthBase80('eng', "B", 120)
setMultiboardCharWidthBase80('eng', "C", 103)
setMultiboardCharWidthBase80('eng', "D", 96)
setMultiboardCharWidthBase80('eng', "E", 131)
setMultiboardCharWidthBase80('eng', "F", 160)
setMultiboardCharWidthBase80('eng', "G", 96)
setMultiboardCharWidthBase80('eng', "H", 90)
setMultiboardCharWidthBase80('eng', "I", 240)
setMultiboardCharWidthBase80('eng', "J", 240)
setMultiboardCharWidthBase80('eng', "K", 120)
setMultiboardCharWidthBase80('eng', "L", 131)
setMultiboardCharWidthBase80('eng', "M", 76)
setMultiboardCharWidthBase80('eng', "N", 90)
setMultiboardCharWidthBase80('eng', "O", 85)
setMultiboardCharWidthBase80('eng', "P", 120)
setMultiboardCharWidthBase80('eng', "Q", 85)
setMultiboardCharWidthBase80('eng', "R", 120)
setMultiboardCharWidthBase80('eng', "S", 131)
setMultiboardCharWidthBase80('eng', "T", 144)
setMultiboardCharWidthBase80('eng', "U", 96)
setMultiboardCharWidthBase80('eng', "V", 120)
setMultiboardCharWidthBase80('eng', "W", 76)
setMultiboardCharWidthBase80('eng', "X", 111)
setMultiboardCharWidthBase80('eng', "Y", 120)
setMultiboardCharWidthBase80('eng', "Z", 111)
setMultiboardCharWidthBase80('eng', "1", 103)
setMultiboardCharWidthBase80('eng', "2", 111)
setMultiboardCharWidthBase80('eng', "3", 111)
setMultiboardCharWidthBase80('eng', "4", 111)
setMultiboardCharWidthBase80('eng', "5", 111)
setMultiboardCharWidthBase80('eng', "6", 111)
setMultiboardCharWidthBase80('eng', "7", 111)
setMultiboardCharWidthBase80('eng', "8", 111)
setMultiboardCharWidthBase80('eng', "9", 111)
setMultiboardCharWidthBase80('eng', "0", 111)
setMultiboardCharWidthBase80('eng', ":", 288)
setMultiboardCharWidthBase80('eng', ";", 288)
setMultiboardCharWidthBase80('eng', ".", 288)
setMultiboardCharWidthBase80('eng', "#", 103)
setMultiboardCharWidthBase80('eng', ",", 288)
setMultiboardCharWidthBase80('eng', " ", 286) --space
setMultiboardCharWidthBase80('eng', "'", 360)
setMultiboardCharWidthBase80('eng', "!", 288)
setMultiboardCharWidthBase80('eng', "$", 131)
setMultiboardCharWidthBase80('eng', "&", 120)
setMultiboardCharWidthBase80('eng', "/", 180)
setMultiboardCharWidthBase80('eng', "(", 206)
setMultiboardCharWidthBase80('eng', ")", 206)
setMultiboardCharWidthBase80('eng', "=", 111)
setMultiboardCharWidthBase80('eng', "?", 180)
setMultiboardCharWidthBase80('eng', "^", 144)
setMultiboardCharWidthBase80('eng', "<", 111)
setMultiboardCharWidthBase80('eng', ">", 111)
setMultiboardCharWidthBase80('eng', "-", 160)
setMultiboardCharWidthBase80('eng', "+", 111)
setMultiboardCharWidthBase80('eng', "*", 144)
setMultiboardCharWidthBase80('eng', "|", 479) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('eng', "~", 144)
setMultiboardCharWidthBase80('eng', "{", 160)
setMultiboardCharWidthBase80('eng', "}", 160)
setMultiboardCharWidthBase80('eng', "[", 206)
setMultiboardCharWidthBase80('eng', "]", 206)
setMultiboardCharWidthBase80('eng', "_", 120)
setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
setMultiboardCharWidthBase80('eng', "\x22", 180) --double quotation mark
setMultiboardCharWidthBase80('eng', "\x40", 85) --at sign
setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
else
--German font size up to patch 1.32
setMultiboardCharWidthBase80('ger', "a", 144)
setMultiboardCharWidthBase80('ger', "b", 144)
setMultiboardCharWidthBase80('ger', "c", 144)
setMultiboardCharWidthBase80('ger', "d", 131)
setMultiboardCharWidthBase80('ger', "e", 144)
setMultiboardCharWidthBase80('ger', "f", 240)
setMultiboardCharWidthBase80('ger', "g", 120)
setMultiboardCharWidthBase80('ger', "h", 144)
setMultiboardCharWidthBase80('ger', "i", 360)
setMultiboardCharWidthBase80('ger', "j", 288)
setMultiboardCharWidthBase80('ger', "k", 144)
setMultiboardCharWidthBase80('ger', "l", 360)
setMultiboardCharWidthBase80('ger', "m", 90)
setMultiboardCharWidthBase80('ger', "n", 144)
setMultiboardCharWidthBase80('ger', "o", 131)
setMultiboardCharWidthBase80('ger', "p", 131)
setMultiboardCharWidthBase80('ger', "q", 131)
setMultiboardCharWidthBase80('ger', "r", 206)
setMultiboardCharWidthBase80('ger', "s", 180)
setMultiboardCharWidthBase80('ger', "t", 206)
setMultiboardCharWidthBase80('ger', "u", 144)
setMultiboardCharWidthBase80('ger', "v", 131)
setMultiboardCharWidthBase80('ger', "w", 96)
setMultiboardCharWidthBase80('ger', "x", 144)
setMultiboardCharWidthBase80('ger', "y", 131)
setMultiboardCharWidthBase80('ger', "z", 144)
setMultiboardCharWidthBase80('ger', "A", 103)
setMultiboardCharWidthBase80('ger', "B", 131)
setMultiboardCharWidthBase80('ger', "C", 120)
setMultiboardCharWidthBase80('ger', "D", 111)
setMultiboardCharWidthBase80('ger', "E", 144)
setMultiboardCharWidthBase80('ger', "F", 180)
setMultiboardCharWidthBase80('ger', "G", 103)
setMultiboardCharWidthBase80('ger', "H", 103)
setMultiboardCharWidthBase80('ger', "I", 288)
setMultiboardCharWidthBase80('ger', "J", 240)
setMultiboardCharWidthBase80('ger', "K", 120)
setMultiboardCharWidthBase80('ger', "L", 144)
setMultiboardCharWidthBase80('ger', "M", 80)
setMultiboardCharWidthBase80('ger', "N", 103)
setMultiboardCharWidthBase80('ger', "O", 96)
setMultiboardCharWidthBase80('ger', "P", 144)
setMultiboardCharWidthBase80('ger', "Q", 90)
setMultiboardCharWidthBase80('ger', "R", 120)
setMultiboardCharWidthBase80('ger', "S", 144)
setMultiboardCharWidthBase80('ger', "T", 144)
setMultiboardCharWidthBase80('ger', "U", 111)
setMultiboardCharWidthBase80('ger', "V", 120)
setMultiboardCharWidthBase80('ger', "W", 76)
setMultiboardCharWidthBase80('ger', "X", 111)
setMultiboardCharWidthBase80('ger', "Y", 120)
setMultiboardCharWidthBase80('ger', "Z", 120)
setMultiboardCharWidthBase80('ger', "1", 288)
setMultiboardCharWidthBase80('ger', "2", 131)
setMultiboardCharWidthBase80('ger', "3", 144)
setMultiboardCharWidthBase80('ger', "4", 120)
setMultiboardCharWidthBase80('ger', "5", 144)
setMultiboardCharWidthBase80('ger', "6", 131)
setMultiboardCharWidthBase80('ger', "7", 144)
setMultiboardCharWidthBase80('ger', "8", 131)
setMultiboardCharWidthBase80('ger', "9", 131)
setMultiboardCharWidthBase80('ger', "0", 131)
setMultiboardCharWidthBase80('ger', ":", 480)
setMultiboardCharWidthBase80('ger', ";", 360)
setMultiboardCharWidthBase80('ger', ".", 480)
setMultiboardCharWidthBase80('ger', "#", 120)
setMultiboardCharWidthBase80('ger', ",", 360)
setMultiboardCharWidthBase80('ger', " ", 288) --space
setMultiboardCharWidthBase80('ger', "'", 480)
setMultiboardCharWidthBase80('ger', "!", 360)
setMultiboardCharWidthBase80('ger', "$", 160)
setMultiboardCharWidthBase80('ger', "&", 96)
setMultiboardCharWidthBase80('ger', "/", 180)
setMultiboardCharWidthBase80('ger', "(", 288)
setMultiboardCharWidthBase80('ger', ")", 288)
setMultiboardCharWidthBase80('ger', "=", 160)
setMultiboardCharWidthBase80('ger', "?", 180)
setMultiboardCharWidthBase80('ger', "^", 144)
setMultiboardCharWidthBase80('ger', "<", 160)
setMultiboardCharWidthBase80('ger', ">", 160)
setMultiboardCharWidthBase80('ger', "-", 144)
setMultiboardCharWidthBase80('ger', "+", 160)
setMultiboardCharWidthBase80('ger', "*", 206)
setMultiboardCharWidthBase80('ger', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('ger', "~", 144)
setMultiboardCharWidthBase80('ger', "{", 240)
setMultiboardCharWidthBase80('ger', "}", 240)
setMultiboardCharWidthBase80('ger', "[", 240)
setMultiboardCharWidthBase80('ger', "]", 288)
setMultiboardCharWidthBase80('ger', "_", 144)
setMultiboardCharWidthBase80('ger', "\x25", 111) --percent
setMultiboardCharWidthBase80('ger', "\x5C", 206) --backslash
setMultiboardCharWidthBase80('ger', "\x22", 240) --double quotation mark
setMultiboardCharWidthBase80('ger', "\x40", 103) --at sign
setMultiboardCharWidthBase80('ger', "\x60", 240) --Gravis (Accent)
--English Font size up to patch 1.32
setMultiboardCharWidthBase80('eng', "a", 144)
setMultiboardCharWidthBase80('eng', "b", 120)
setMultiboardCharWidthBase80('eng', "c", 131)
setMultiboardCharWidthBase80('eng', "d", 120)
setMultiboardCharWidthBase80('eng', "e", 131)
setMultiboardCharWidthBase80('eng', "f", 240)
setMultiboardCharWidthBase80('eng', "g", 120)
setMultiboardCharWidthBase80('eng', "h", 131)
setMultiboardCharWidthBase80('eng', "i", 360)
setMultiboardCharWidthBase80('eng', "j", 288)
setMultiboardCharWidthBase80('eng', "k", 144)
setMultiboardCharWidthBase80('eng', "l", 360)
setMultiboardCharWidthBase80('eng', "m", 80)
setMultiboardCharWidthBase80('eng', "n", 131)
setMultiboardCharWidthBase80('eng', "o", 120)
setMultiboardCharWidthBase80('eng', "p", 120)
setMultiboardCharWidthBase80('eng', "q", 120)
setMultiboardCharWidthBase80('eng', "r", 206)
setMultiboardCharWidthBase80('eng', "s", 160)
setMultiboardCharWidthBase80('eng', "t", 206)
setMultiboardCharWidthBase80('eng', "u", 131)
setMultiboardCharWidthBase80('eng', "v", 144)
setMultiboardCharWidthBase80('eng', "w", 90)
setMultiboardCharWidthBase80('eng', "x", 131)
setMultiboardCharWidthBase80('eng', "y", 144)
setMultiboardCharWidthBase80('eng', "z", 144)
setMultiboardCharWidthBase80('eng', "A", 103)
setMultiboardCharWidthBase80('eng', "B", 120)
setMultiboardCharWidthBase80('eng', "C", 103)
setMultiboardCharWidthBase80('eng', "D", 103)
setMultiboardCharWidthBase80('eng', "E", 131)
setMultiboardCharWidthBase80('eng', "F", 160)
setMultiboardCharWidthBase80('eng', "G", 103)
setMultiboardCharWidthBase80('eng', "H", 96)
setMultiboardCharWidthBase80('eng', "I", 288)
setMultiboardCharWidthBase80('eng', "J", 240)
setMultiboardCharWidthBase80('eng', "K", 120)
setMultiboardCharWidthBase80('eng', "L", 131)
setMultiboardCharWidthBase80('eng', "M", 76)
setMultiboardCharWidthBase80('eng', "N", 96)
setMultiboardCharWidthBase80('eng', "O", 85)
setMultiboardCharWidthBase80('eng', "P", 131)
setMultiboardCharWidthBase80('eng', "Q", 85)
setMultiboardCharWidthBase80('eng', "R", 120)
setMultiboardCharWidthBase80('eng', "S", 131)
setMultiboardCharWidthBase80('eng', "T", 144)
setMultiboardCharWidthBase80('eng', "U", 103)
setMultiboardCharWidthBase80('eng', "V", 120)
setMultiboardCharWidthBase80('eng', "W", 76)
setMultiboardCharWidthBase80('eng', "X", 111)
setMultiboardCharWidthBase80('eng', "Y", 120)
setMultiboardCharWidthBase80('eng', "Z", 111)
setMultiboardCharWidthBase80('eng', "1", 206)
setMultiboardCharWidthBase80('eng', "2", 131)
setMultiboardCharWidthBase80('eng', "3", 131)
setMultiboardCharWidthBase80('eng', "4", 111)
setMultiboardCharWidthBase80('eng', "5", 131)
setMultiboardCharWidthBase80('eng', "6", 120)
setMultiboardCharWidthBase80('eng', "7", 131)
setMultiboardCharWidthBase80('eng', "8", 111)
setMultiboardCharWidthBase80('eng', "9", 120)
setMultiboardCharWidthBase80('eng', "0", 111)
setMultiboardCharWidthBase80('eng', ":", 360)
setMultiboardCharWidthBase80('eng', ";", 360)
setMultiboardCharWidthBase80('eng', ".", 360)
setMultiboardCharWidthBase80('eng', "#", 103)
setMultiboardCharWidthBase80('eng', ",", 360)
setMultiboardCharWidthBase80('eng', " ", 288) --space
setMultiboardCharWidthBase80('eng', "'", 480)
setMultiboardCharWidthBase80('eng', "!", 360)
setMultiboardCharWidthBase80('eng', "$", 131)
setMultiboardCharWidthBase80('eng', "&", 120)
setMultiboardCharWidthBase80('eng', "/", 180)
setMultiboardCharWidthBase80('eng', "(", 240)
setMultiboardCharWidthBase80('eng', ")", 240)
setMultiboardCharWidthBase80('eng', "=", 111)
setMultiboardCharWidthBase80('eng', "?", 180)
setMultiboardCharWidthBase80('eng', "^", 144)
setMultiboardCharWidthBase80('eng', "<", 131)
setMultiboardCharWidthBase80('eng', ">", 131)
setMultiboardCharWidthBase80('eng', "-", 180)
setMultiboardCharWidthBase80('eng', "+", 111)
setMultiboardCharWidthBase80('eng', "*", 180)
setMultiboardCharWidthBase80('eng', "|", 480) --2 vertical bars in a row escape to one. So you could print 960 ones in a line, 480 would display. Maybe need to adapt to this before calculating string width.
setMultiboardCharWidthBase80('eng', "~", 144)
setMultiboardCharWidthBase80('eng', "{", 240)
setMultiboardCharWidthBase80('eng', "}", 240)
setMultiboardCharWidthBase80('eng', "[", 240)
setMultiboardCharWidthBase80('eng', "]", 240)
setMultiboardCharWidthBase80('eng', "_", 120)
setMultiboardCharWidthBase80('eng', "\x25", 103) --percent
setMultiboardCharWidthBase80('eng', "\x5C", 180) --backslash
setMultiboardCharWidthBase80('eng', "\x22", 206) --double quotation mark
setMultiboardCharWidthBase80('eng', "\x40", 96) --at sign
setMultiboardCharWidthBase80('eng', "\x60", 206) --Gravis (Accent)
end
end
Debug.beginFile("IngameConsole")
--[[
--------------------------
----| Ingame Console |----
--------------------------
/**********************************************
* Allows you to use the following ingame commands:
* "-exec <code>" to execute any code ingame.
* "-console" to start an ingame console interpreting any further chat input as code and showing both return values of function calls and error messages. Furthermore, the print function will print
* directly to the console after it got started. You can still look up all print messages in the F12-log.
***********************
* -------------------
* |Using the console|
* -------------------
* Any (well, most) chat input by any player after starting the console is interpreted as code and directly executed. You can enter terms (like 4+5 or just any variable name), function calls (like print("bla"))
* and set-statements (like y = 5). If the code has any return values, all of them are printed to the console. Erroneous code will print an error message.
* Chat input starting with a hyphen is being ignored by the console, i.e. neither executed as code nor printed to the console. This allows you to still use other chat commands like "-exec" without prompting errors.
***********************
* ------------------
* |Multiline-Inputs|
* ------------------
* You can prevent a chat input from being immediately executed by preceeding it with the '>' character. All lines entered this way are halted, until any line not starting with '>' is being entered.
* The first input without '>' will execute all halted lines (and itself) in one chunk.
* Example of a chat input (the console will add an additional '>' to every line):
* >function a(x)
* >return x
* end
***********************
* Note that multiline inputs don't accept pure term evaluations, e.g. the following input is not supported and will prompt an error, while the same lines would have worked as two single-line inputs:
* >x = 5
* x
***********************
* -------------------
* |Reserved Keywords|
* -------------------
* The following keywords have a reserved functionality, i.e. are direct commands for the console and will not be interpreted as code:
* - 'help' - will show a list of all reserved keywords along very short explanations.
* - 'exit' - will shut down the console
* - 'share' - will share the players console with every other player, allowing others to read and write into it. Will force-close other players consoles, if they have one active.
* - 'clear' - will clear all text from the console, except the word 'clear'
* - 'lasttrace' - will show the stack trace of the latest error that occured within IngameConsole
* - 'show' - will show the console, after it was accidently hidden (you can accidently hide it by showing another multiboard, while the console functionality is still up and running).
* - 'printtochat' - will let the print function return to normal behaviour (i.e. print to the chat instead of the console).
* - 'printtoconsole'- will let the print function print to the console (which is default behaviour).
* - 'autosize on' - will enable automatic console resize depending on the longest string in the display. This is turned on by default.
* - 'autosize off' - will disable automatic console resize and instead linebreak long strings into multiple lines.
* - 'textlang eng' - lets the console use english Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
* - 'textlang ger' - lets the console use german Wc3 text language font size to compute linebreaks (look in your Blizzard launcher settings to find out)
***********************
* --------------
* |Paste Helper|
* --------------
* @Luashine has created a tool that simplifies pasting multiple lines of code from outside Wc3 into the IngameConsole.
* This is particularly useful, when you want to execute a large chunk of testcode containing several linebreaks.
* Goto: https://github.com/Luashine/wc3-debug-console-paste-helper#readme
*
*************************************************/
--]]
----------------
--| Settings |--
----------------
---@class IngameConsole
IngameConsole = {
--Settings
numRows = 20 ---@type integer Number of Rows of the console (multiboard), excluding the title row. So putting 20 here will show 21 rows, first being the title row.
, autosize = true ---@type boolean Defines, whether the width of the main Column automatically adjusts with the longest string in the display.
, currentWidth = 0.5 ---@type number Current and starting Screen Share of the console main column.
, mainColMinWidth = 0.3 ---@type number Minimum Screen share of the console main column.
, mainColMaxWidth = 0.8 ---@type number Maximum Scren share of the console main column.
, tsColumnWidth = 0.06 ---@type number Screen Share of the Timestamp Column
, linebreakBuffer = 0.008 ---@type number Screen Share that is added to longest string in display to calculate the screen share for the console main column. Compensates for the small inaccuracy of the String Width function.
, maxLinebreaks = 8 ---@type integer Defines the maximum amount of linebreaks, before the remaining output string will be cut and not further displayed.
, printToConsole = true ---@type boolean defines, if the print function should print to the console or to the chat
, sharedConsole = false ---@type boolean defines, if the console is displayed to each player at the same time (accepting all players input) or if all players much start their own console.
, showTraceOnError = false ---@type boolean defines, if the console shows a trace upon printing errors. Usually not too useful within console, because you have just initiated the erroneous call.
, textLanguage = 'eng' ---@type string text language of your Wc3 installation, which influences font size (look in the settings of your Blizzard launcher). Currently only supports 'eng' and 'ger'.
, colors = {
timestamp = "bbbbbb" ---@type string Timestamp Color
, singleLineInput = "ffffaa" ---@type string Color to be applied to single line console inputs
, multiLineInput = "ffcc55" ---@type string Color to be applied to multi line console inputs
, returnValue = "00ffff" ---@type string Color applied to return values
, error = "ff5555" ---@type string Color to be applied to errors resulting of function calls
, keywordInput = "ff00ff" ---@type string Color to be applied to reserved keyword inputs (console reserved keywords)
, info = "bbbbbb" ---@type string Color to be applied to info messages from the console itself (for instance after creation or after printrestore)
}
--Privates
, numCols = 2 ---@type integer Number of Columns of the console (multiboard). Adjusting this requires further changes on code base.
, player = nil ---@type player player for whom the console is being created
, currentLine = 0 ---@type integer Current Output Line of the console.
, inputload = '' ---@type string Input Holder for multi-line-inputs
, output = {} ---@type string[] Array of all output strings
, outputTimestamps = {} ---@type string[] Array of all output string timestamps
, outputWidths = {} ---@type number[] remembers all string widths to allow for multiboard resize
, trigger = nil ---@type trigger trigger processing all inputs during console lifetime
, multiboard = nil ---@type multiboard
, timer = nil ---@type timer gets started upon console creation to measure timestamps
, errorHandler = nil ---@type fun(errorMsg:string):string error handler to be used within xpcall. We create one per console to make it compatible with console-specific settings.
, lastTrace = '' ---@type string trace of last error occured within console. To be printed via reserved keyword "lasttrace"
--Statics
, keywords = {} ---@type table<string,function> saves functions to be executed for all reserved keywords
, playerConsoles = {} ---@type table<player,IngameConsole> Consoles currently being active. up to one per player.
, originalPrint = print ---@type function original print function to restore, after the console gets closed.
}
IngameConsole.__index = IngameConsole
IngameConsole.__name = 'IngameConsole'
------------------------
--| Console Creation |--
------------------------
---Creates and opens up a new console.
---@param consolePlayer player player for whom the console is being created
---@return IngameConsole
function IngameConsole.create(consolePlayer)
local new = {} ---@type IngameConsole
setmetatable(new, IngameConsole)
---setup Object data
new.player = consolePlayer
new.output = {}
new.outputTimestamps = {}
new.outputWidths = {}
--Timer
new.timer = CreateTimer()
TimerStart(new.timer, 3600., true, nil) --just to get TimeElapsed for printing Timestamps.
--Trigger to be created after short delay, because otherwise it would fire on "-console" input immediately and lead to stack overflow.
new:setupTrigger()
--Multiboard
new:setupMultiboard()
--Create own error handler per console to be compatible with console-specific settings
new:setupErrorHandler()
--Share, if settings say so
if IngameConsole.sharedConsole then
new:makeShared() --we don't have to exit other players consoles, because we look for the setting directly in the class and there just logically can't be other active consoles.
end
--Welcome Message
new:out('info', 0, false,
"Console started. Any further chat input will be executed as code, except when beginning with \x22-\x22.")
return new
end
---Creates the multiboard used for console display.
function IngameConsole:setupMultiboard()
self.multiboard = CreateMultiboard()
MultiboardSetRowCount(self.multiboard, self.numRows + 1) --title row adds 1
MultiboardSetColumnCount(self.multiboard, self.numCols)
MultiboardSetTitleText(self.multiboard, "Console")
local mbitem
for col = 1, self.numCols do
for row = 1, self.numRows + 1 do --Title row adds 1
mbitem = MultiboardGetItem(self.multiboard, row - 1, col - 1)
MultiboardSetItemStyle(mbitem, true, false)
MultiboardSetItemValueColor(mbitem, 255, 255, 255, 255) -- Colors get applied via text color code
MultiboardSetItemWidth(mbitem, (col == 1 and self.tsColumnWidth) or self.currentWidth)
MultiboardReleaseItem(mbitem)
end
end
mbitem = MultiboardGetItem(self.multiboard, 0, 0)
MultiboardSetItemValue(mbitem, "|cffffcc00Timestamp|r")
MultiboardReleaseItem(mbitem)
mbitem = MultiboardGetItem(self.multiboard, 0, 1)
MultiboardSetItemValue(mbitem, "|cffffcc00Line|r")
MultiboardReleaseItem(mbitem)
self:showToOwners()
end
---Creates the trigger that responds to chat events.
function IngameConsole:setupTrigger()
self.trigger = CreateTrigger()
TriggerRegisterPlayerChatEvent(self.trigger, self.player, "", false) --triggers on any input of self.player
TriggerAddCondition(self.trigger, Condition(function() return string.sub(GetEventPlayerChatString(), 1, 1) ~= '-' end)) --console will not react to entered stuff starting with '-'. This still allows to use other chat orders like "-exec".
TriggerAddAction(self.trigger, function() self:processInput(GetEventPlayerChatString()) end)
end
---Creates an Error Handler to be used by xpcall below.
---Adds stack trace plus formatting to the message.
function IngameConsole:setupErrorHandler()
self.errorHandler = function(errorMsg)
errorMsg = Debug.getLocalErrorMsg(errorMsg)
local _, tracePiece, lastFile = nil, "", errorMsg:match("^.-:") or "<unknown>" -- errors on objects created within Ingame Console don't have a file and linenumber. Consider "x = {}; x[nil] = 5".
local fullMsg = errorMsg ..
"\nTraceback (most recent call first):\n" .. (errorMsg:match("^.-:\x25d+") or "<unknown>")
--Get Stack Trace. Starting at depth 5 ensures that "error", "messageHandler", "xpcall" and the input error message are not included.
for loopDepth = 5, 50 do --get trace on depth levels up to 50
---@diagnostic disable-next-line: cast-local-type, assign-type-mismatch
_, tracePiece = pcall(error, "", loopDepth) ---@type boolean, string
tracePiece = Debug.getLocalErrorMsg(tracePiece)
if #tracePiece > 0 then --some trace pieces can be empty, but there can still be valid ones beyond that
fullMsg = fullMsg ..
" <- " ..
(
(tracePiece:match("^.-:") == lastFile) and tracePiece:match(":\x25d+"):sub(2, -1) or
tracePiece:match("^.-:\x25d+"))
lastFile = tracePiece:match("^.-:")
end
end
self.lastTrace = fullMsg
return "ERROR: " .. (self.showTraceOnError and fullMsg or errorMsg)
end
end
---Shares this console with all players.
function IngameConsole:makeShared()
local player
for i = 0, GetBJMaxPlayers() - 1 do
player = Player(i)
if (GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING) and (IngameConsole.playerConsoles[player] ~= self) then --second condition ensures that the player chat event is not added twice for the same player.
IngameConsole.playerConsoles[player] = self
TriggerRegisterPlayerChatEvent(self.trigger, player, "", false) --triggers on any input
end
end
self.sharedConsole = true
end
---------------------
--| In |--
---------------------
---Processes a chat string. Each input will be printed. Incomplete multiline-inputs will be halted until completion. Completed inputs will be converted to a function and executed. If they have an output, it will be printed.
---@param inputString string
function IngameConsole:processInput(inputString)
--if the input is a reserved keyword, conduct respective actions and skip remaining actions.
if IngameConsole.keywords[inputString] then --if the input string is a reserved keyword
self:out('keywordInput', 1, false, inputString)
IngameConsole.keywords[inputString](self) --then call the method with the same name. IngameConsole.keywords["exit"](self) is just self.keywords:exit().
return
end
--if the input is a multi-line-input, queue it into the string buffer (inputLoad), but don't yet execute anything
if string.sub(inputString, 1, 1) == '>' then --multiLineInput
inputString = string.sub(inputString, 2, -1)
self:out('multiLineInput', 2, false, inputString)
self.inputload = self.inputload .. inputString .. '\r' --carriage return
else --if the input is either singleLineInput OR the last line of multiLineInput, execute the whole thing.
self:out(self.inputload == '' and 'singleLineInput' or 'multiLineInput', 1, false, inputString)
self.inputload = self.inputload .. inputString
local loadedFunc, errorMsg = load("return " .. self.inputload) --adds return statements, if possible (works for term statements)
if loadedFunc == nil then
loadedFunc, errorMsg = load(self.inputload)
end
self.inputload = '' --empty inputload before execution of pcall. pcall can break (rare case, can for example be provoked with metatable.__tostring = {}), which would corrupt future console inputs.
--manually catch case, where the input did not define a proper Lua statement (i.e. loadfunc is nil)
local results = loadedFunc and table.pack(xpcall(loadedFunc, self.errorHandler)) or
{ false, "Input is not a valid Lua-statement: " .. errorMsg }
--output error message (unsuccessful case) or return values (successful case)
if not results[1] then --results[1] is the error status that pcall always returns. False stands for: error occured.
self:out('error', 0, true, results[2]) -- second result of pcall is the error message in case an error occured
elseif results.n > 1 then --Check, if there was at least one valid output argument. We check results.n instead of results[2], because we also get nil as a proper return value this way.
self:out('returnValue', 0, true, table.unpack(results, 2, results.n))
end
end
end
----------------------
--| Out |--
----------------------
-- split color codes, split linebreaks, print lines separately, print load-errors, update string width, update text, error handling with stack trace.
---Duplicates Color coding around linebreaks to make each line printable separately.
---Operates incorrectly on lookalike color codes invalidated by preceeding escaped vertical bar (like "||cffffcc00bla|r").
---Also operates incorrectly on multiple color codes, where the first is missing the end sequence (like "|cffffcc00Hello |cff0000ffWorld|r")
---@param inputString string
---@return string, integer
function IngameConsole.spreadColorCodes(inputString)
local replacementTable = {} --remembers all substrings to be replaced and their replacements.
for foundInstance, color in inputString:gmatch("((|c\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x).-|r)") do
replacementTable[foundInstance] = foundInstance:gsub("(\r?\n)", "|r\x251" .. color)
end
return inputString:gsub("((|c\x25x\x25x\x25x\x25x\x25x\x25x\x25x\x25x).-|r)", replacementTable)
end
---Concatenates all inputs to one string, spreads color codes around line breaks and prints each line to the console separately.
---@param colorTheme? '"timestamp"'| '"singleLineInput"' | '"multiLineInput"' | '"result"' | '"keywordInput"' | '"info"' | '"error"' | '"returnValue"' Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil equals no colorTheme, i.e. white color)
---@param numIndentations integer Number of '>' chars that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@param ... any the things to be printed in the console.
function IngameConsole:out(colorTheme, numIndentations, hideTimestamp, ...)
local inputs = table.pack(...)
for i = 1, inputs.n do
inputs[i] = tostring(inputs[i]) --apply tostring on every input param in preparation for table.concat
end
--Concatenate all inputs (4-space-separated)
local printOutput = table.concat(inputs, ' ', 1, inputs.n)
printOutput = printOutput:find("(\r?\n)") and IngameConsole.spreadColorCodes(printOutput) or printOutput
local substrStart, substrEnd = 1, 1
local numLinebreaks, completePrint = 0, true
repeat
substrEnd = (printOutput:find("(\r?\n)", substrStart) or 0) - 1
numLinebreaks, completePrint = self:lineOut(colorTheme, numIndentations, hideTimestamp, numLinebreaks,
printOutput:sub(substrStart, substrEnd))
hideTimestamp = true
substrStart = substrEnd + 2
until substrEnd == -1 or numLinebreaks > self.maxLinebreaks
if substrEnd ~= -1 or not completePrint then
self:lineOut('info', 0, false, 0,
"Previous value not entirely printed after exceeding maximum number of linebreaks. Consider adjusting 'IngameConsole.maxLinebreaks'.")
end
self:updateMultiboard()
end
---Prints the given string to the console with the specified colorTheme and the specified number of indentations.
---Only supports one-liners (no \n) due to how multiboards work. Will add linebreaks though, if the one-liner doesn't fit into the given multiboard space.
---@param colorTheme? '"timestamp"'| '"singleLineInput"' | '"multiLineInput"' | '"result"' | '"keywordInput"' | '"info"' | '"error"' | '"returnValue"' Decides about the color to be applied. Currently accepted: 'timestamp', 'singleLineInput', 'multiLineInput', 'result', nil. (nil equals no colorTheme, i.e. white color)
---@param numIndentations integer Number of greater '>' chars that shall preceed the output
---@param hideTimestamp boolean Set to false to hide the timestamp column and instead show a "->" symbol.
---@param numLinebreaks integer
---@param printOutput string the line to be printed in the console.
---@return integer numLinebreaks, boolean hasPrintedEverything returns true, if everything could be printed. Returns false otherwise (can happen for very long strings).
function IngameConsole:lineOut(colorTheme, numIndentations, hideTimestamp, numLinebreaks, printOutput)
--add preceeding greater chars
printOutput = ('>'):rep(numIndentations) .. printOutput
--Print a space instead of the empty string. This allows the console to identify, if the string has already been fully printed (see while-loop below).
if printOutput == '' then
printOutput = ' '
end
--Compute Linebreaks.
local linebreakWidth = ((self.autosize and self.mainColMaxWidth) or self.currentWidth)
local partialOutput = nil
local maxPrintableCharPosition
local printWidth
while string.len(printOutput) > 0 and numLinebreaks <= self.maxLinebreaks do --break, if the input string has reached length 0 OR when the maximum number of linebreaks would be surpassed.
--compute max printable substring (in one multiboard line)
maxPrintableCharPosition, printWidth = IngameConsole.getLinebreakData(printOutput,
linebreakWidth - self.linebreakBuffer, self.textLanguage)
--adds timestamp to the first line of any output
if numLinebreaks == 0 then
partialOutput = printOutput:sub(1, numIndentations) ..
(
(
IngameConsole.colors[colorTheme] and
"|cff" ..
IngameConsole.colors[colorTheme] ..
printOutput:sub(numIndentations + 1, maxPrintableCharPosition) .. "|r") or
printOutput:sub(numIndentations + 1, maxPrintableCharPosition)) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
table.insert(self.outputTimestamps,
"|cff" ..
IngameConsole.colors['timestamp'] ..
((hideTimestamp and ' ->') or IngameConsole.formatTimerElapsed(TimerGetElapsed(self.timer)))
.. "|r")
else
partialOutput = (
IngameConsole.colors[colorTheme] and
"|cff" .. IngameConsole.colors[colorTheme] .. printOutput:sub(1, maxPrintableCharPosition) .. "|r")
or printOutput:sub(1, maxPrintableCharPosition) --Colorize the output string, if a color theme was specified. IngameConsole.colors[colorTheme] can be nil.
table.insert(self.outputTimestamps, ' ..') --need a dummy entry in the timestamp list to make it line-progress with the normal output.
end
numLinebreaks = numLinebreaks + 1
--writes output string and width to the console tables.
table.insert(self.output, partialOutput)
table.insert(self.outputWidths, printWidth + self.linebreakBuffer) --remember the Width of this printed string to adjust the multiboard size in case. 0.5 percent is added to avoid the case, where the multiboard width is too small by a tiny bit, thus not showing some string without spaces.
--compute remaining string to print
printOutput = string.sub(printOutput, maxPrintableCharPosition + 1, -1) --remaining string until the end. Returns empty string, if there is nothing left
end
self.currentLine = #self.output
return numLinebreaks, string.len(printOutput) == 0 --printOutput is the empty string, if and only if everything has been printed
end
---Lets the multiboard show the recently printed lines.
function IngameConsole:updateMultiboard()
local startIndex = math.max(self.currentLine - self.numRows, 0) --to be added to loop counter to get to the index of output table to print
local outputIndex = 0
local maxWidth = 0.
local mbitem
for i = 1, self.numRows do --doesn't include title row (index 0)
outputIndex = i + startIndex
mbitem = MultiboardGetItem(self.multiboard, i, 0)
MultiboardSetItemValue(mbitem, self.outputTimestamps[outputIndex] or '')
MultiboardReleaseItem(mbitem)
mbitem = MultiboardGetItem(self.multiboard, i, 1)
MultiboardSetItemValue(mbitem, self.output[outputIndex] or '')
MultiboardReleaseItem(mbitem)
maxWidth = math.max(maxWidth, self.outputWidths[outputIndex] or 0.) --looping through non-defined widths, so need to coalesce with 0
end
--Adjust Multiboard Width, if necessary.
maxWidth = math.min(math.max(maxWidth, self.mainColMinWidth), self.mainColMaxWidth)
if self.autosize and self.currentWidth ~= maxWidth then
self.currentWidth = maxWidth
for i = 1, self.numRows + 1 do
mbitem = MultiboardGetItem(self.multiboard, i - 1, 1)
MultiboardSetItemWidth(mbitem, maxWidth)
MultiboardReleaseItem(mbitem)
end
self:showToOwners() --reshow multiboard to update item widths on the frontend
end
end
---Shows the multiboard to all owners (one or all players)
function IngameConsole:showToOwners()
if self.sharedConsole or GetLocalPlayer() == self.player then
MultiboardDisplay(self.multiboard, true)
MultiboardMinimize(self.multiboard, false)
end
end
---Formats the elapsed time as "mm: ss. hh" (h being a hundreds of a sec)
function IngameConsole.formatTimerElapsed(elapsedInSeconds)
return string.format("\x2502d: \x2502.f. \x2502.f", elapsedInSeconds // 60, math.fmod(elapsedInSeconds, 60.) // 1,
math.fmod(elapsedInSeconds, 1) * 100)
end
---Computes the max printable substring for a given string and a given linebreakWidth (regarding a single line of console).
---Returns both the substrings last char position and its total width in the multiboard.
---@param stringToPrint string the string supposed to be printed in the multiboard console.
---@param linebreakWidth number the maximum allowed width in one line of the console, before a string must linebreak
---@param textLanguage string 'ger' or 'eng'
---@return integer maxPrintableCharPosition, number printWidth
function IngameConsole.getLinebreakData(stringToPrint, linebreakWidth, textLanguage)
local loopWidth = 0.
local bytecodes = table.pack(string.byte(stringToPrint, 1, -1))
for i = 1, bytecodes.n do
loopWidth = loopWidth + string.charMultiboardWidth(bytecodes[i], textLanguage)
if loopWidth > linebreakWidth then
return i - 1, loopWidth - string.charMultiboardWidth(bytecodes[i], textLanguage)
end
end
return bytecodes.n, loopWidth
end
-------------------------
--| Reserved Keywords |--
-------------------------
---Exits the Console
---@param self IngameConsole
function IngameConsole.keywords.exit(self)
DestroyMultiboard(self.multiboard)
DestroyTrigger(self.trigger)
DestroyTimer(self.timer)
IngameConsole.playerConsoles[self.player] = nil
if next(IngameConsole.playerConsoles) == nil then --set print function back to original, when no one has an active console left.
print = IngameConsole.originalPrint
end
end
---Lets the console print to chat
---@param self IngameConsole
function IngameConsole.keywords.printtochat(self)
self.printToConsole = false
self:out('info', 0, false, "The print function will print to the normal chat.")
end
---Lets the console print to itself (default)
---@param self IngameConsole
function IngameConsole.keywords.printtoconsole(self)
self.printToConsole = true
self:out('info', 0, false, "The print function will print to the console.")
end
---Shows the console in case it was hidden by another multiboard before
---@param self IngameConsole
function IngameConsole.keywords.show(self)
self:showToOwners() --might be necessary to do, if another multiboard has shown up and thereby hidden the console.
self:out('info', 0, false, "Console is showing.")
end
---Prints all available reserved keywords plus explanations.
---@param self IngameConsole
function IngameConsole.keywords.help(self)
self:out('info', 0, false, "The Console currently reserves the following keywords:")
self:out('info', 0, false, "'help' shows the text you are currently reading.")
self:out('info', 0, false, "'exit' closes the console.")
self:out('info', 0, false, "'lasttrace' shows the stack trace of the latest error that occured within IngameConsole.")
self:out('info', 0, false,
"'share' allows other players to read and write into your console, but also force-closes their own consoles.")
self:out('info', 0, false, "'clear' clears all text from the console.")
self:out('info', 0, false, "'show' shows the console. Sensible to use, when displaced by another multiboard.")
self:out('info', 0, false, "'printtochat' lets Wc3 print text to normal chat again.")
self:out('info', 0, false, "'printtoconsole' lets Wc3 print text to the console (default).")
self:out('info', 0, false,
"'autosize on' enables automatic console resize depending on the longest line in the display.")
self:out('info', 0, false, "'autosize off' retains the current console size.")
self:out('info', 0, false,
"'textlang eng' will use english text installation font size to compute linebreaks (default).")
self:out('info', 0, false, "'textlang ger' will use german text installation font size to compute linebreaks.")
self:out('info', 0, false,
"Preceeding a line with '>' prevents immediate execution, until a line not starting with '>' has been entered.")
end
---Clears the display of the console.
---@param self IngameConsole
function IngameConsole.keywords.clear(self)
self.output = {}
self.outputTimestamps = {}
self.outputWidths = {}
self.currentLine = 0
self:out('keywordInput', 1, false, 'clear') --we print 'clear' again. The keyword was already printed by self:processInput, but cleared immediately after.
end
---Shares the console with other players in the same game.
---@param self IngameConsole
function IngameConsole.keywords.share(self)
for _, console in pairs(IngameConsole.playerConsoles) do
if console ~= self then
IngameConsole.keywords['exit'](console) --share was triggered during console runtime, so there potentially are active consoles of others players that need to exit.
end
end
self:makeShared()
self:showToOwners() --showing it to the other players.
self:out('info', 0, false,
"The console of player " .. GetConvertedPlayerId(self.player) .. " is now shared with all players.")
end
---Enables auto-sizing of console (will grow and shrink together with text size)
---@param self IngameConsole
IngameConsole.keywords["autosize on"] = function(self)
self.autosize = true
self:out('info', 0, false, "The console will now change size depending on its content.")
end
---Disables auto-sizing of console
---@param self IngameConsole
IngameConsole.keywords["autosize off"] = function(self)
self.autosize = false
self:out('info', 0, false, "The console will retain the width that it currently has.")
end
---Lets linebreaks be computed by german font size
---@param self IngameConsole
IngameConsole.keywords["textlang ger"] = function(self)
self.textLanguage = 'ger'
self:out('info', 0, false, "Linebreaks will now compute with respect to german text installation font size.")
end
---Lets linebreaks be computed by english font size
---@param self IngameConsole
IngameConsole.keywords["textlang eng"] = function(self)
self.textLanguage = 'eng'
self:out('info', 0, false, "Linebreaks will now compute with respect to english text installation font size.")
end
---Prints the stack trace of the latest error that occured within IngameConsole.
---@param self IngameConsole
IngameConsole.keywords["lasttrace"] = function(self)
self:out('error', 0, false, self.lastTrace)
end
--------------------
--| Main Trigger |--
--------------------
do
--Actions to be executed upon typing -exec
local function execCommand_Actions()
local input = string.sub(GetEventPlayerChatString(), 7, -1)
print("Executing input: |cffffff44" .. input .. "|r")
--try preceeding the input by a return statement (preparation for printing below)
local loadedFunc, errorMsg = load("return " .. input)
if not loadedFunc then --if that doesn't produce valid code, try without return statement
loadedFunc, errorMsg = load(input)
end
--execute loaded function in case the string defined a valid function. Otherwise print error.
if errorMsg then
print("|cffff5555Invalid Lua-statement: " .. Debug.getLocalErrorMsg(errorMsg) .. "|r")
else
---@diagnostic disable-next-line: param-type-mismatch
local results = table.pack(Debug.try(loadedFunc))
if results[1] ~= nil or results.n > 1 then
for i = 1, results.n do
results[i] = tostring(results[i])
end
--concatenate all function return values to one colorized string
print("|cff00ffff" .. table.concat(results, ' ', 1, results.n) .. "|r")
end
end
end
local function execCommand_Condition()
return string.sub(GetEventPlayerChatString(), 1, 6) == "-exec "
end
local function startIngameConsole()
--if the triggering player already has a console, show that console and stop executing further actions
if IngameConsole.playerConsoles[GetTriggerPlayer()] then
IngameConsole.playerConsoles[GetTriggerPlayer()]:showToOwners()
return
end
--create Ingame Console object
IngameConsole.playerConsoles[GetTriggerPlayer()] = IngameConsole.create(GetTriggerPlayer())
--overwrite print function
print = function(...)
IngameConsole.originalPrint(...) --the new print function will also print "normally", but clear the text immediately after. This is to add the message to the F12-log.
if IngameConsole.playerConsoles[GetLocalPlayer()] and
IngameConsole.playerConsoles[GetLocalPlayer()].printToConsole then
ClearTextMessages() --clear text messages for all players having an active console
end
for player, console in pairs(IngameConsole.playerConsoles) do
if console.printToConsole and (player == console.player) then --player == console.player ensures that the console only prints once, even if the console was shared among all players
console:out(nil, 0, false, ...)
end
end
end
end
---Creates the triggers listening to "-console" and "-exec" chat input.
---Being executed within DebugUtils (MarkGameStart overwrite).
function IngameConsole.createTriggers()
--Exec
local execTrigger = CreateTrigger()
TriggerAddCondition(execTrigger, Condition(execCommand_Condition))
TriggerAddAction(execTrigger, execCommand_Actions)
--Real Console
local consoleTrigger = CreateTrigger()
TriggerAddAction(consoleTrigger, startIngameConsole)
--Events
for i = 0, GetBJMaxPlayers() - 1 do
TriggerRegisterPlayerChatEvent(execTrigger, Player(i), "-exec ", false)
TriggerRegisterPlayerChatEvent(consoleTrigger, Player(i), "-console", true)
end
end
end
Debug.endFile()
Debug = Debug or {beginFile = DoNothing, getLine = DoNothing, endFile = DoNothing} ; Debug.beginFile "Total Initialization" --Debug API fallback.
-- Total Initialization v5.3.0.0 Preview by Bribe
-- Your one-stop shop for initialization and requirements.
do
--'OnInit' governs all initialization from Total Initialization.
OnInit = {}
local library = {} --You can change this to false if you don't use "Require" nor the OnInit.library API.
--CONFIGURABLE LEGACY API FUNCTION:
local function assignLegacyAPI(g)
local OnInit, _ENV = OnInit, g
OnGlobalInit = OnInit; OnTrigInit = OnInit.trig; OnMapInit = OnInit.map; OnGameStart = OnInit.final --Global Initialization Lite API
--OnMainInit = OnInit.main; OnLibraryInit = OnInit.library; OnGameInit = OnInit.final --short-lived experimental API
--onGlobalInit = OnInit; onTriggerInit = OnInit.trig; onInitialization = OnInit.map; onGameStart = OnInit.final --original Global Initialization API
--OnTriggerInit = OnInit.trig; OnInitialization = OnInit.map --Forsakn's Ordered Indices API
end
--END CONFIGURABLES
---@class Require
---@overload async fun(requirementName:string):any
---@field __index fun(optionalName:string):async fun(requirementName:string):any Enable syntax for Require.optionally "SomeRequirement"
local requirer = {}
--"Require" only works within an OnInit callback.
--
--Syntax for strict requirements that throw errors if not found: Require "SomeLibrary"
--
--Syntax for requirements that give up if the required library or variable are not found: Require.optionally "SomeLibrary"
Require = requirer
local _G, rawget, insert = _G, rawget, table.insert
local call = try or pcall --'try' is extremely useful; found on https://www.hiveworkshop.com/threads/debug-utils-ingame-console-etc.330758/post-3552846
local fCall = library and function(...) coroutine.wrap(call)(...) end or call
local initFuncQueue = {}
local function runInitializers(name, continue)
if initFuncQueue[name] then
for _,func in ipairs(initFuncQueue[name]) do
fCall(func, requirer)
end
initFuncQueue[name] = nil
end
if library then library:resume() end
if continue then continue() end
end
do
local function hook(hookName, continue)
local hookedFunc = rawget(_G, hookName)
if hookedFunc then
rawset(_G, hookName, function()
hookedFunc()
runInitializers(hookName, continue)
end)
else
runInitializers(hookName, continue)
end
end
hook("InitGlobals", function()
hook("InitCustomTriggers", function()
hook("RunInitializationTriggers")
end)
end)
hook("MarkGameStarted", function()
if library then
for _,func in ipairs(library.yielded) do
func(nil, true) --run errors for missing requirements.
end
for _,func in pairs(library.modules) do
func(true) --run errors for modules that aren't required.
end
end
OnInit=nil;Require=nil
end)
end
local function addUserFunc(initName, libraryName, func, debugLineNum, incDebugLevel)
if not func then
func = libraryName
else
assert(type(libraryName)=="string")
if debugLineNum then
Debug.beginFile(libraryName, incDebugLevel and 7 or 6, debugLineNum)
end
if library then
func = library:create(libraryName, func)
end
end
assert(type(func) == "function")
initFuncQueue[initName] = initFuncQueue[initName] or {}
insert(initFuncQueue[initName], func)
if initName == "root" then
runInitializers "root"
end
end
local function createInit(name)
---Calls the user's initialization function during the map's loading process. The first argument should either be the init function,
---or it should be the string to give the initializer a name (works similarly to a module name/identically to a vJass library name).
---
---To use requirements, call Require "LibraryName" or Require.optionally "LibraryName". Alternatively, the callback function can take
---the "Require" table as a single parameter: OnInit(function(import) import "ThisIsTheSameAsRequire" end).
---
---OnInit is called after InitGlobals and is the standard point to initialize......
---OnInit.trig is called after InitCustomTriggers, and is useful for removing hooks that should only apply to GUI events......
---OnInit.map is the last point in initialization before the loading screen is completed......
---OnInit.final occurs immediately after the loading screen has disappeared, and the game has started.
---@param libraryNameOrInitFunc string|fun(require?:Require):any
---@param userInitFunc? fun(require?:Require):any
---@param debugLineNum? integer
---@param incDebugLevel? boolean
return function(libraryNameOrInitFunc, userInitFunc, debugLineNum, incDebugLevel)
addUserFunc(name, libraryNameOrInitFunc, userInitFunc, debugLineNum, incDebugLevel)
end
end
OnInit.global = createInit "InitGlobals"
OnInit.trig = createInit "InitCustomTriggers"
OnInit.map = createInit "RunInitializationTriggers"
OnInit.final = createInit "MarkGameStarted"
function OnInit:__call(libraryNameOrInitFunc, userInitFunc, debugLineNum)
if userInitFunc or type(libraryNameOrInitFunc)=="function" then
self.global(libraryNameOrInitFunc, userInitFunc, debugLineNum, true) --Calling OnInit directly defaults to OnInit.global (AKA OnGlobalInit)
elseif libraryNameOrInitFunc == "end" then
return Debug.getLine(3)
elseif library then
library:declare(libraryNameOrInitFunc) --API handler for OnInit "Custom initializer"
else
error("Bad OnInit args: "..tostring(libraryNameOrInitFunc) .. ", " .. tostring(userInitFunc))
end
end
setmetatable(OnInit, OnInit)
do --if you don't need the initializers for "root", "config" and "main", you can delete this do...end block.
local gmt = getmetatable(_G) or getmetatable(setmetatable(_G, {}))
local ___newindex = gmt.__newindex or rawset
local newIndex
function newIndex(g, key, val)
if key == "main" or key == "config" then
if key == "main" then
runInitializers "root"
end
___newindex(g, key, function()
if key == "main" and gmt.__newindex == newIndex then
gmt.__newindex = ___newindex --restore the original __newindex if no further hooks on __newindex exist.
end
runInitializers(key)
val()
end)
else
___newindex(g, key, val)
end
end
gmt.__newindex = newIndex
OnInit.root = createInit "root" -- Runs immediately during the Lua root, but is yieldable (allowing requirements) and pcalled.
OnInit.config = createInit "config" -- Runs when "config" is called. Credit to @Luashine: https://www.hiveworkshop.com/threads/inject-main-config-from-we-trigger-code-like-jasshelper.338201/
OnInit.main = createInit "main" -- Runs when "main" is called. Idea from @Tasyen: https://www.hiveworkshop.com/threads/global-initialization.317099/post-3374063
end
if library then
library.packed = {}
library.yielded = {}
library.declared = {}
library.modules = {}
function library:pack(name, ...) self.packed[name] = table.pack(...) end
function library:resume()
if self.yielded[1] then
local continue, tempQueue, forceOptional
::initLibraries::
repeat
continue=false
self.yielded, tempQueue = {}, self.yielded
for _,func in ipairs(tempQueue) do
if func(forceOptional) then
continue=true --Something was initialized; therefore further systems might be able to initialize.
else
insert(self.yielded, func) --If the queued initializer returns false, that means its requirement wasn't met, so we re-queue it.
end
end
until not continue or not self.yielded[1]
if self.declared[1] then
self.declared, tempQueue = {}, self.declared
for _,func in ipairs(tempQueue) do
func() --unfreeze any custom initializers.
end
elseif not forceOptional then
forceOptional = true
else
return
end
goto initLibraries
end
end
local function declareName(name, initialValue)
assert(type(name)=="string")
assert(library.packed[name]==nil)
library.packed[name] = initialValue and {true,n=1}
end
function library:create(name, userFunc)
assert(type(userFunc)=="function")
declareName(name, false) --declare itself as a non-loaded library.
return function()
self:pack(name, userFunc(requirer)) --pack return values to allow multiple values to be communicated.
if self.packed[name].n==0 then
self:pack(name, true) --No values were returned; therefore simply package the value as "true"
end
end
end
---@async
function library:declare(name)
declareName(name, true) --declare itself as a loaded library.
local co = coroutine.running()
insert(self.declared, function() coroutine.resume(co) end)
coroutine.yield(co) --yields the calling function until after all currently-queued initializers have run.
end
local processRequirement
---@async
function processRequirement(optional, requirement, explicitSource)
if type(optional) == "string" then
optional, requirement, explicitSource = true, optional, requirement --optional requirement (processed by the __index method)
else
optional = false --strict requirement (processed by the __call method)
end
local source = explicitSource or _G
assert(type(source)=="table")
assert(type(requirement)=="string")
::reindex::
local subSource, subReq = requirement:match("([\x25w_]+)\x25.(.+)") --Check if user is requiring using "table.property" syntax
if subSource and subReq then
source, requirement = processRequirement(subSource, source), subReq --If the container is nil, yield until it is not.
if type(source)=="table" then
explicitSource = source
goto reindex --check for further nested properties ("table.property.subProperty.anyOthers").
else
return --The source table for the requirement wasn't found, so disregard the rest (this only happens with optional requirements).
end
end
local function loadRequirement(unpack)
local package = rawget(source, requirement)
if not package and not explicitSource then
if library.modules[requirement] then
library.modules[requirement]()
end
package = library.packed[requirement]
if unpack and type(package)=="table" then
return table.unpack(package, 1, package.n) --using unpack allows any number of values to be returned by the required library.
end
end
return package
end
local co, loaded
local function checkReqs(forceOptional, printErrors)
if not loaded then
loaded = loadRequirement()
loaded = loaded or optional and (loaded==nil or forceOptional)
if loaded then
if co then coroutine.resume(co) end --resume only if it was yielded in the first place.
return loaded
elseif printErrors then
coroutine.resume(co, true)
end
end
end
if not checkReqs() then --only yield if the requirement doesn't already exist.
co = coroutine.running()
insert(library.yielded, checkReqs)
if coroutine.yield(co) then
error("Missing Requirement: "..requirement) --handle the error within the user's function to get an accurate stack trace via the "try" function.
end
end
return loadRequirement(true)
end
requirer.__call = processRequirement
requirer.__index = function() return processRequirement end --syntax is not working on hover - issue reported at https://github.com/sumneko/lua-language-server/issues/1716
setmetatable(requirer, requirer)
local module = createInit "module"
---@param name string
---@param func? fun(require?:Require):any
---@param debugLineNum? integer
OnInit.module = function(name, func, debugLineNum)
if func then
local userFunc = func
func = function(require)
local co = coroutine.running()
library.modules[name] = function()
library.modules[name] = nil
coroutine.resume(co)
end
if coroutine.yield() then
error("Module declared but not required: "..name) --works similarly to Go; if you don't need a module, then don't include it in your map.
end
return userFunc(require)
end
end
module(name, func, debugLineNum)
end
end
if assignLegacyAPI then --This block handles legacy code.
---Allows packaging multiple requirements into one table and queues the initialization for later.
---@param initList table|string
---@param userFunc function
---@async
function OnInit.library(initList, userFunc)
local typeOf = type(initList)
assert(typeOf=="table" or typeOf=="string")
assert(type(userFunc) == "function")
local function caller(use)
if typeOf=="string" then
use(initList)
else
for _,initName in ipairs(initList) do
use(initName)
end
if initList.optional then
for _,initName in ipairs(initList.optional) do
use.lazily(initName)
end
end
end
end
if initList.name then OnInit(initList.name, caller) else OnInit(caller) end
end
local legacyTable = {}
assignLegacyAPI(legacyTable, OnInit)
for key,func in pairs(legacyTable) do rawset(_G, key, func) end
OnInit.final(function()
for key in pairs(legacyTable) do _G[key] = nil end
end)
end
end
Debug.endFile()
--[[
--------------------------------------------------------------------
AddHook version 5.1.1.1
Author: Bribe
Special Thanks: Jampion and Eikonium
--------------------------------------------------------------------
"AddHook" allows dynamic function overriding and cascading hook callbacks in Lua, empowering systems such as
Global Variable Remapper and CreateEvent (which, in turn, empower systems such as Damage Engine and Spell Event).
AddHook is a function which returns two functions:
1) The next hook callback or original native
2) A function to call to remove the hook.
As of version 5.1, the hooked function is turned into a table, which allows syntax like "CreateTimer.old", which
calls the next hooked function (or the native function, if no other hooked functions exist). The name of this
extension can be anything you want - "native, oldFunc, original", whatever you find to be the most fitting for you.
"Classic" way of hooking in Lua is shown below:
BJDebugMsg = print
The below is the most simple translation within the AddHook system:
AddHook("BJDebugMsg", print)
A small showcase of what you can do with the API:
AddHook("CreateTimer", function()
if recycleTimer then
return recycleStack.pop()
elseif newTimer then
return CreateTimer.original()
end
end)
AddHook("DestroyTimer", function(whichTimer)
if recycleTimer then
recycleStack.insert(whichTimer)
elseif destroyTimer then
DestroyTimer.old(whichTimer)
end
end)
*Note that the names "original" and "old" are just fluff. As long as you are using them from within the callback function,
you can give them any name to call the original function (or next hook).
If you are outside of the callback function, then denoting a property like "native" or "oldFunction" or such will always
call the native function (rather than trigger any hooked callbacks). However, this will fail if the native isn't hooked.
]]------------------------------------------------------------------
do
local max = math.max
local funcProxyTable = {}
local tableMetatable = {
__call = function(self, ...) --the new way of calling hooks, introduced in 5.1
self.current = #self
local result = self[self.current][1](...)
self.current = 0
return result
end,
__index = function(self) --Using the __index method means that non-indexed names will default to accessing the next/orignal function.
self.current = max(self.current - 1, 0)
return self[self.current][1]
end
}
---Insert or remove a hook callback
---@param hooks table
---@param index integer
---@param value? table
local function editList(hooks, index, value)
local top = #hooks
if index > top then
hooks[index] = value --simply add the index to the top of the stack
else
if value then
table.insert(hooks, index, value)
index = index + 1
top = top + 1
else
table.remove(hooks, index)
top = top - 1
end
for i = index, top do
hooks[i].setIndex(i) --Remap subsequent indices
end
end
end
local function createHookTable(native, current)
return setmetatable({[0]={native}, current=current}, tableMetatable)
end
---@param nativeKey any Usually a string (the name of the old function you wish to hook)
---@param callback function The function you want to run when the native is called. The args and return values would normally mimic the function that is hooked.
---@param priority? number Defaults to 0. Hooks are called in order of highest priority down to lowest priority.
---@param hostTable? table Defaults to _G, which is the table that stores all global variables.
---@param default? function If the native does not exist in the host table, use this default instead.
---@param usesMetatable? boolean Defaults to true if the "default" parameter is given.
---@return fun(params_of_native?:any):any callNative
---@return fun(remove_all_hooks?:boolean) callRemoveHook
function AddHook(nativeKey, callback, priority, hostTable, default, usesMetatable)
priority = priority or 0
hostTable = hostTable or _G
local function getNative()
return rawget(hostTable, nativeKey) or default or error("Nothing could be hooked at: "..tostring(nativeKey))
end
local proxy
if usesMetatable or (default and usesMetatable == nil) then
--Index the hook to the metatable instead of the user's given table. Create a new metatable in case none existed.
hostTable = getmetatable(hostTable) or getmetatable(setmetatable(hostTable, {}))
funcProxyTable[hostTable] = funcProxyTable[hostTable] or {}
proxy = funcProxyTable[hostTable][nativeKey] or createHookTable(getNative())
funcProxyTable[hostTable][nativeKey] = proxy
end
local index = 1
local hooks = proxy or getNative()
local typeOf = type(hooks)
if proxy and #proxy == 0 then
rawset(hostTable, nativeKey, function(...) --metatable methods like __index cannot be impersonated by a table that uses metamethods such as __call.
return proxy[#proxy][1](...) --I learned this the hard way when trying to find out why my hooks weren't working on GlobalRemap after Hook 5.1.
end)
elseif typeOf == "table" then
local exitwhen = #hooks
repeat
if hooks[index].priority > priority then break end --Search manually for an index based on the priority of all other hooks.
index = index + 1
until index > exitwhen
elseif typeOf == "function" then
hooks = createHookTable(hooks, 0)
rawset(hostTable, nativeKey, hooks)
else
error("Tried to hook an incorrect type: "..typeOf)
end
editList(hooks, index, {callback, priority = priority, setIndex = function(val) index = val end})
return proxy and function(...)
return hooks[index - 1][1](...) --used for metatables (no need to track the current position)
end or function(...)
hooks.current = index - 1
return hooks[index - 1][1](...) --used for native tables (tracks the current position)
end,
function(removeAll)
if removeAll or index == 1 and #hooks == 1 then
rawset(hostTable, nativeKey, hooks[0][1]) --Remove all hooks by restoring the original function to the host table. The native is stored at index [0][1]
else
editList(hooks, index)
end
end
end
end
OnInit("GlobalRemap", function(needs)
-- needs "AddHook" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Hook.lua
--[[
--------------------------------------------------------------------------------------
Global Variable Remapper v1.3.2 by Bribe
- Turns normal GUI variable references into function calls that integrate seamlessly
with a Lua framework.
API:
GlobalRemap(variableStr[, getterFunc, setterFunc])
@variableStr is a string such as "udg_MyVariable"
@getterFunc is a function that takes nothing but returns the expected value when
"udg_MyVariable" is referenced.
@setterFunc is a function that takes a single argument (the value that is being
assigned) and allows you to do what you want when someone uses "Set MyVariable = SomeValue".
The function doesn't need to do anything nor return anything. Enables read-only
GUI variables for the first time in WarCraft 3 history.
GlobalRemapArray(variableStr[, getterFunc, setterFunc])
@variableStr is a string such as "udg_MyVariableArray"
@getterFunc is a function that takes the index of the array and returns the
expected value when "MyVariableArray" is referenced.
@setterFunc is a function that takes two arguments: the index of the array and the
value the user is trying to assign. The function doesn't return anything.
----------------------------------------------------------------------------------------]]
local default = DoNothing
local getters,setters = {},{}
local oldGet, oldSet
oldGet = AddHook("__index", function(tab, index)
if getters[index]~=nil then
return getters[index]()
end
return oldGet(tab, index)
end, 0, _G, rawget)
oldSet = AddHook("__newindex", function(tab, index, val)
if setters[index]~=nil then
setters[index](val)
else
oldSet(tab, index, val)
end
end, 0, _G, rawset)
---Remap a non-array global variable
---@param var string
---@param getFunc? fun():any
---@param setFunc? fun(value:any)
function GlobalRemap(var, getFunc, setFunc)
_G[var] = nil --Delete the variable from the global table.
getters[var] = getFunc or default --Assign a function that returns what should be returned when this variable is referenced.
setters[var] = setFunc or default --Assign a function that captures the value the variable is attempting to be set to.
end
---Remap a global variable array
---@param var string
---@param getFunc? fun(index : any): any
---@param setFunc? fun(index : any, val : any)
function GlobalRemapArray(var, getFunc, setFunc) --will get inserted into GlobalRemap after testing.
getFunc = getFunc or default
setFunc = setFunc or default
_G[var] = setmetatable({}, {
__index = function(_, index) return getFunc(index) end,
__newindex = function(_, index, val) setFunc(index, val) end
})
end
end)
if Debug then Debug.beginFile "Influa" end
--[[
Influa (WarCraft 3 infused with Lua) automatically deals with memory leaks and modernizes the triggering & coding experience for users.
Credits:
Bribe, Tasyen, Dr Super Good, HerlySQR, Eikonium, AGD
Transforms rects, locations, groups, forces and BJ hashtable wrappers into Lua tables, which are automatically garbage collected.
Provides RegisterAnyPlayerUnitEvent to cut down on handle count and simplify syntax for Lua users while benefitting GUI (this was also a very widely used vJass resource).
Optionally hides the "boolexpr" native type by having Filter/Condition/And/Or/Not return 'function'. While mainly intended to mitigate memory leaks via boolexpr recycling,
this also allows syntax like 'TriggerAddCondition(trig, function)', which was possible in vJass due to the way JassHelper automatically added the Filter/Condition wrapper.
Provides GUI.enumUnitsInRect/InRange/Selected/etc. which replaces the 'group' parameter with a function that takes a unit, allowing immediate action without a group variable.
Provides GUI.loopArray for safe iteration over a __jarray.
Updated: 13th Jan 2022
Uses optionally:
https://github.com/BribeFromTheHive/Lua-Core/blob/main/Total_Initialization.lua
https://github.com/BribeFromTheHive/Lua-Core/blob/main/Hook.lua
https://github.com/BribeFromTheHive/Lua-Core/blob/main/Global_Variable_Remapper.lua
https://github.com/BribeFromTheHive/Lua-Core/blob/main/UnitEvent.lua
--]]
---@diagnostic disable: duplicate-set-field
---@class GUI
GUI = { typecast = function(self) return self end }
---@type fun(event: playerunitevent, userFunc: function, skip?: boolean):function?
RegisterAnyPlayerUnitEvent = nil
--Configurables
GUI.settings = {
USE_GLOBAL_REMAP = false, --set to true if you want GUI to have extended functionality such as "udg_HashTableArray" (which gives GUI an infinite supply of shared hashtables)
USE_UNIT_EVENT = false, --set to true if you have UnitEvent in your map and want to automatically remove units from their unit groups if they are removed from the game.
USE_BOOLEXPRS = OnInit, --will only overwrite boolexprs if Total Initialization is in the map.
}
--Define common variables to be utilized throughout the script.
local _G = _G
local unpack = table.unpack
--[[-----------------------------------------------------------------------------------------
__jarray expander by Bribe
This snippet will ensure that objects used as indices in udg_ arrays will be automatically
cleaned up when the garbage collector runs, and tries to re-use metatables whenever possible.
-------------------------------------------------------------------------------------------]]
GUI.mts = {}
GUI.weakKeys = {__mode="k"} --ensures tables with non-nilled objects as keys will be garbage collected.
---Re-define __jarray.
---@param default? any
---@param tab? table
---@return table
function __jarray(default, tab) ---@diagnostic disable-line lowercase-global
local mt
if default then
GUI.mts[default]=GUI.mts[default] or {
__index=function()
return default
end,
__mode="k"
}
mt=GUI.mts[default]
else
mt=GUI.weakKeys
end
return setmetatable(tab or {}, mt)
end
--have to do a wide search for all arrays in the variable editor. The WarCraft 3 _G table is HUGE,
--and without editing the war3map.lua file manually, it is not possible to rewrite it in advance.
for k,v in pairs(_G) do
if type(v) == "table" and string.sub(k, 1, 4)=="udg_" then
__jarray(v[0], v)
end
end
---Add this safe iterator function for jarrays.
---@param whichTable table
---@param func fun(index:integer, value:any)
function GUI.loopArray(whichTable, func)
for i=rawget(whichTable, 0)~=nil and 0 or 1, #whichTable do
func(i, rawget(whichTable, i))
end
end
if GUI.settings.USE_BOOLEXPRS then
---@class boolexpr: function
GUI.filter = Filter
Filter = GUI.typecast
Condition = GUI.typecast
And = function(func1, func2) return function() return func1() and func2() end end
Or = function(func1, func2) return function() return func1() or func2() end end
Not = function(func) return function() return not func() end end
OnInit.main(function()
GUI.staticFilter = GUI.filter(function() return GUI.tempFilter() end)
end)
function GUI.tempBoolExpr(func)
if func then
GUI.tempFilter = func
return GUI.staticFilter
end
end
local boolexprs = __jarray() ---@type {[fun():boolean] : boolexpr}
local oldDest = DestroyBoolExpr
function DestroyBoolExpr(func)
if boolexprs[func] then
oldDest(boolexprs[func])
boolexprs[func] = nil
end
end
local function newBoolExpr(func)
if func then
boolexprs[func] = boolexprs[func] or GUI.filter(func)
return boolexprs[func]
end
end
local oldTAC = TriggerAddCondition
---@overload fun(trig: trigger, func: fun():boolean?)
function TriggerAddCondition(trig, func)
return oldTAC(trig, newBoolExpr(func)) ---@diagnostic disable-line: return-type-mismatch
end
local function hook3rd(name)
local old = _G[name]; _G[name] = function(a,b,func)
return old(a,b,newBoolExpr(func))
end
end
hook3rd "TriggerRegisterEnterRegion"
hook3rd "TriggerRegisterLeaveRegion"
local function hook4th(name)
local old = _G[name]; _G[name] = function(a,b,c,func)
return old(a,b,c,newBoolExpr(func))
end
end
hook4th "TriggerRegisterPlayerUnitEvent"
hook4th "TriggerRegisterFilterUnitEvent"
hook4th "TriggerRegisterUnitInRange"
end
--[=============[
• HASHTABLES •
--]=============]
do --[[
GUI hashtable converter by Tasyen and Bribe
Converts GUI hashtables API into Lua Tables, overwrites StringHashBJ and GetHandleIdBJ to permit
typecasting, bypasses the 256 hashtable limit by avoiding hashtables, provides the variable
"HashTableArray", which automatically creates hashtables for you as needed (so you don't have to
initialize them each time).
]]
StringHashBJ = GUI.typecast
GetHandleIdBJ = GUI.typecast
local function load(whichHashTable,parentKey)
local index = whichHashTable[parentKey]
if not index then
index=__jarray()
whichHashTable[parentKey]=index
end
return index
end
if GUI.settings.USE_GLOBAL_REMAP then
OnInit(function()
local remap = Require "Remap"
local hashes = __jarray()
remap("udg_HashTableArray", function(index)
return load(hashes, index)
end)
end)
end
local last
GetLastCreatedHashtableBJ=function() return last end
function InitHashtableBJ() last=__jarray() ; return last end
local function saveInto(value, childKey, parentKey, whichHashTable)
if childKey and parentKey and whichHashTable then
load(whichHashTable, parentKey)[childKey] = value
end
end
local function loadFrom(childKey, parentKey, whichHashTable, default)
if childKey and parentKey and whichHashTable then
local val = load(whichHashTable, parentKey)[childKey]
return val ~= nil and val or default
end
end
SaveIntegerBJ = saveInto
SaveRealBJ = saveInto
SaveBooleanBJ = saveInto
SaveStringBJ = saveInto
local function createDefault(default)
return function(childKey, parentKey, whichHashTable)
return loadFrom(childKey, parentKey, whichHashTable, default)
end
end
local loadNumber = createDefault(0)
LoadIntegerBJ = loadNumber
LoadRealBJ = loadNumber
LoadBooleanBJ = createDefault(false)
LoadStringBJ = createDefault("")
do
local sub = string.sub
for key in pairs(_G) do
if sub(key, -8)=="HandleBJ" then
local str=sub(key, 1,4)
if str=="Save" then _G[key] = saveInto
elseif str=="Load" then _G[key] = loadFrom end
end
end
end
function HaveSavedValue(childKey, _, parentKey, whichHashTable)
return load(whichHashTable, parentKey)[childKey] ~= nil
end
FlushParentHashtableBJ = function(whichHashTable)
for key in pairs(whichHashTable) do
whichHashTable[key]=nil
end
end
function FlushChildHashtableBJ(whichHashTable, parentKey)
whichHashTable[parentKey]=nil
end
end
--[===========================[
• LOCATIONS (POINTS IN GUI) •
--]===========================]
do
local oldLocation = Location
local location
do
local oldRemove = RemoveLocation
local oldGetX = GetLocationX
local oldGetY = GetLocationY
local oldRally = GetUnitRallyPoint
function GetUnitRallyPoint(unit)
local removeThis = oldRally(unit) --Actually needs to create a location for a brief moment, as there is no GetUnitRallyX/Y
local loc = {oldGetX(removeThis), oldGetY(removeThis)}
oldRemove(removeThis)
return loc
end
end
RemoveLocation = DoNothing
function Location(x,y)
return {x,y}
end
do
local oldMoveLoc = MoveLocation
local oldGetZ=GetLocationZ
function GUI.getCoordZ(x,y)
GUI.getCoordZ = function(x,y)
oldMoveLoc(location, x, y)
return oldGetZ(location)
end
location = oldLocation(x,y)
return oldGetZ(location)
end
end
function GetLocationX(loc) return loc[1] end
function GetLocationY(loc) return loc[2] end
function GetLocationZ(loc)
return GUI.getCoordZ(loc[1], loc[2])
end
function MoveLocation(loc, x, y)
loc[1]=x
loc[2]=y
end
local function fakeCreate(varName, suffix)
local getX=_G[varName.."X"]
local getY=_G[varName.."Y"]
_G[varName..(suffix or "Loc")]=function(obj) return {getX(obj), getY(obj)} end
end
fakeCreate("GetUnit")
fakeCreate("GetOrderPoint")
fakeCreate("GetSpellTarget")
fakeCreate("CameraSetupGetDestPosition")
fakeCreate("GetCameraTargetPosition")
fakeCreate("GetCameraEyePosition")
fakeCreate("BlzGetTriggerPlayerMouse", "Position")
fakeCreate("GetStartLocation")
BlzSetSpecialEffectPositionLoc = function(effect, loc)
local x,y=loc[1],loc[2]
BlzSetSpecialEffectPosition(effect, x, y, GUI.getCoordZ(x,y))
end
---@param oldVarName string
---@param newVarName string
---@param index integer needed to determine which of the parameters calls for a location.
local function hook(oldVarName, newVarName, index)
local new = _G[newVarName]
local func
if index==1 then
func=function(loc, ...)
return new(loc[1], loc[2], ...)
end
elseif index==2 then
func=function(a, loc, ...)
return new(a, loc[1], loc[2], ...)
end
else--index==3
func=function(a, b, loc, ...)
return new(a, b, loc[1], loc[2], ...)
end
end
_G[oldVarName] = func
end
hook("IsLocationInRegion", "IsPointInRegion", 2)
hook("IsUnitInRangeLoc", "IsUnitInRangeXY", 2)
hook("IssuePointOrderLoc", "IssuePointOrder", 3)
IssuePointOrderLocBJ =IssuePointOrderLoc
hook("IssuePointOrderByIdLoc", "IssuePointOrderById", 3)
hook("IsLocationVisibleToPlayer", "IsVisibleToPlayer", 1)
hook("IsLocationFoggedToPlayer", "IsFoggedToPlayer", 1)
hook("IsLocationMaskedToPlayer", "IsMaskedToPlayer", 1)
hook("CreateFogModifierRadiusLoc", "CreateFogModifierRadius", 3)
hook("AddSpecialEffectLoc", "AddSpecialEffect", 2)
hook("AddSpellEffectLoc", "AddSpellEffect", 3)
hook("AddSpellEffectByIdLoc", "AddSpellEffectById", 3)
hook("SetBlightLoc", "SetBlight", 2)
hook("DefineStartLocationLoc", "DefineStartLocation", 2)
hook("GroupEnumUnitsInRangeOfLoc", "GroupEnumUnitsInRange", 2)
hook("GroupEnumUnitsInRangeOfLocCounted", "GroupEnumUnitsInRangeCounted", 2)
hook("GroupPointOrderLoc", "GroupPointOrder", 3)
GroupPointOrderLocBJ =GroupPointOrderLoc
hook("GroupPointOrderByIdLoc", "GroupPointOrderById", 3)
hook("MoveRectToLoc", "MoveRectTo", 2)
hook("RegionAddCellAtLoc", "RegionAddCell", 2)
hook("RegionClearCellAtLoc", "RegionClearCell", 2)
hook("CreateUnitAtLoc", "CreateUnit", 3)
hook("CreateUnitAtLocByName", "CreateUnitByName", 3)
hook("SetUnitPositionLoc", "SetUnitPosition", 2)
hook("ReviveHeroLoc", "ReviveHero", 2)
hook("SetFogStateRadiusLoc", "SetFogStateRadius", 3)
---@param min table location
---@param max table location
---@return rect newRect
RectFromLoc = function(min, max)
return Rect(min[1], min[2], max[1], max[2])
end
---@param whichRect rect
---@param min table location
---@param max table location
SetRectFromLoc = function(whichRect, min, max)
SetRect(whichRect, min[1], min[2], max[1], max[2])
end
end
--[=============================[
• GROUPS (UNIT GROUPS IN GUI) •
--]=============================]
do
local mainGroup = bj_lastCreatedGroup
DestroyGroup(bj_suspendDecayFleshGroup)
DestroyGroup(bj_suspendDecayBoneGroup)
DestroyGroup=DoNothing
CreateGroup=function() return {indexOf={}} end
bj_lastCreatedGroup=CreateGroup() ---@diagnostic disable-line lowercase-global
bj_suspendDecayFleshGroup=CreateGroup() ---@diagnostic disable-line lowercase-global
bj_suspendDecayBoneGroup=CreateGroup() ---@diagnostic disable-line lowercase-global
---@class group: { [integer]: unit }
---@field indexOf { [unit]: integer }
local groups
if GUI.settings.USE_UNIT_EVENT then
groups = {}
function GroupClear(group)
if group then
local u
for i=1, #group do
u=group[i]
groups[u]=nil
group.indexOf[u]=nil
group[i]=nil
end
end
end
else
function GroupClear(group)
if group then
for i=1, #group do
group.indexOf[group[i]]=nil
group[i]=nil
end
end
end
end
function GroupAddUnit(group, unit)
if group and unit and not group.indexOf[unit] then
local pos = #group+1
group.indexOf[unit]=pos
group[pos]=unit
if groups then
groups[unit] = groups[unit] or __jarray()
groups[unit][group]=true
end
return true
end
return false
end
function GroupRemoveUnit(group, unit)
local indexOf = group and unit and group.indexOf
if indexOf then
local pos = indexOf[unit]
if pos then
local size = #group
if pos ~= size then
group[pos] = group[size] --fixed thanks to Trokkin
indexOf[group[size]] = pos
end
group[size]=nil
indexOf[unit]=nil
if groups then
groups[unit][group]=nil
end
end
return true
end
return false
end
function IsUnitInGroup(unit, group) return unit and group and (group.indexOf[unit]~=nil) end
function FirstOfGroup(group) return group and group[1] end
local enumUnit; GetEnumUnit=function() return enumUnit end
function GUI.forGroup(group, code)
for i=1, #group do
code(group[i])
end
end
ForGroup = function(group, code)
if group and code then
local cache = enumUnit
GUI.forGroup(group, function(unit)
enumUnit=unit
code(unit)
end)
enumUnit=cache
end
end
do
local oldUnitAt=BlzGroupUnitAt
function BlzGroupUnitAt(group, index)
return group and group[index+1]
end
local oldGetSize=BlzGroupGetSize
local function groupAction(code)
for i=0, oldGetSize(mainGroup)-1 do
code(oldUnitAt(mainGroup, i))
end
end
local function createGroupAPI(name, pos)
local varStr = "GroupEnumUnits"..name
local new=_G[varStr]
if GUI.settings.USE_BOOLEXPRS then
local convert = GUI.tempBoolExpr
local old = new
if pos == 3 then
new = function(a,b,filt,n)
local cache = GUI.tempFilter
old(a,b,convert(filt),n)
GUI.tempFilter = cache
end
elseif pos == 4 then
new = function(a,b,c,filt,n)
local cache = GUI.tempFilter
old(a,b,c,convert(filt),n)
GUI.tempFilter = cache
end
else --pos==5
new = function(a,b,c,d,filt,n)
local cache = GUI.tempFilter
old(a,b,c,d,convert(filt),n)
GUI.tempFilter = cache
end
end
end
_G[varStr]=function(group, ...)
if group then
new(mainGroup, ...)
GroupClear(group)
groupAction(function(unit)
GroupAddUnit(group, unit)
end)
end
end
--Provide API for Lua users who just want to efficiently run code, without caring about the group itself.
GUI["enumUnits"..name]=function(code, ...)
new(mainGroup, ...)
groupAction(code)
end
end
createGroupAPI("OfType", 3)
createGroupAPI("OfPlayer", 3)
createGroupAPI("OfTypeCounted", 3)
createGroupAPI("InRect", 3)
createGroupAPI("InRectCounted", 3)
createGroupAPI("InRange", 5)
createGroupAPI("InRangeOfLoc", 4)
createGroupAPI("InRangeCounted", 5)
createGroupAPI("InRangeOfLocCounted", 4)
createGroupAPI("Selected", 3)
end
for _,name in ipairs {
"ImmediateOrder",
"ImmediateOrderById",
"PointOrder",
"PointOrderById",
"TargetOrder",
"TargetOrderById"
} do
local new = _G["Issue"..name]
_G["Group"..name]=function(group, ...)
for i=1, #group do
new(group[i], ...)
end
end
end
GroupTrainOrderByIdBJ = GroupImmediateOrderById
BlzGroupGetSize=function(group) return group and #group or 0 end
function GroupAddGroup(group, add)
if not group or not add then return end
GUI.forGroup(add, function(unit)
GroupAddUnit(group, unit)
end)
end
function GroupRemoveGroup(group, remove)
if not group or not remove then return end
GUI.forGroup(remove, function(unit)
GroupRemoveUnit(group, unit)
end)
end
GroupPickRandomUnit=function(group)
return group and group[1] and group[GetRandomInt(1,#group)] or nil
end
IsUnitGroupEmptyBJ=function(group)
return not group or not group[1]
end
ForGroupBJ=ForGroup
CountUnitsInGroup=BlzGroupGetSize
BlzGroupAddGroupFast=GroupAddGroup
BlzGroupRemoveGroupFast=GroupRemoveGroup
GroupPickRandomUnitEnum=nil ---@diagnostic disable-line
CountUnitsInGroupEnum=nil ---@diagnostic disable-line
GroupAddGroupEnum=nil ---@diagnostic disable-line
GroupRemoveGroupEnum=nil ---@diagnostic disable-line
if groups then
OnInit(function(import)
import "UnitEvent"
UnitEvent.onRemoval(function(data)
local u = data.unit
local g = groups[u]
if g then
for _,group in pairs(g) do
GroupRemoveUnit(group,u)
end
end
end)
end)
end
end
--[========================[
• RECTS (REGIONS IN GUI) •
--]========================]
do
local oldRect, rect = Rect, nil
function Rect(...) return {...} end
local oldSetRect = SetRect
function SetRect(r, mix, miy, max, may)
r[1]=mix
r[2]=miy
r[3]=max
r[4]=may
end
do
local oldWorld = GetWorldBounds
local getMinX=GetRectMinX
local getMinY=GetRectMinY
local getMaxX=GetRectMaxX
local getMaxY=GetRectMaxY
local remover = RemoveRect
RemoveRect=DoNothing
local newWorld
function GetWorldBounds()
if not newWorld then
local w = oldWorld()
newWorld = {getMinX(w),getMinY(w),getMaxX(w),getMaxY(w)}
remover(w)
end
return {unpack(newWorld)}
end
GetEntireMapRect = GetWorldBounds
end
function GetRectMinX(r) return r[1] end
function GetRectMinY(r) return r[2] end
function GetRectMaxX(r) return r[3] end
function GetRectMaxY(r) return r[4] end
function GetRectCenterX(r) return (r[1] + r[3])/2 end
function GetRectCenterY(r) return (r[2] + r[4])/2 end
function MoveRectTo(r, x, y)
x = x - GetRectCenterX(r)
y = y - GetRectCenterY(r)
SetRect(r, r[1]+x, r[2]+y, r[3]+x, r[4]+y)
end
---@param varName string
---@param index integer needed to determine which of the parameters calls for a rect.
local function hook(varName, index)
local old = _G[varName]
local func
if index==1 then
if GUI.settings.USE_BOOLEXPRS and (varName == "EnumItemsInRect" or varName == "EnumDestructablesInRect") then
local convert = GUI.tempBoolExpr
func=function(rct, filt, code)
oldSetRect(rect, unpack(rct))
local cache = GUI.tempFilter
old(rect, convert(filt), code)
GUI.tempFilter = cache
end
else
func=function(rct, ...)
oldSetRect(rect, unpack(rct))
return old(rect, ...)
end
end
elseif index==2 then
func=function(a, rct, ...)
oldSetRect(rect, unpack(rct))
return old(a, rect, ...)
end
else--index==3
func=function(a, b, rct, ...)
oldSetRect(rect, unpack(rct))
return old(a, b, rect, ...)
end
end
_G[varName] = function(...)
if not rect then rect = oldRect(0,0,32,32) end
_G[varName] = func
return func(...)
end
end
hook("EnumDestructablesInRect", 1)
hook("EnumItemsInRect", 1)
hook("AddWeatherEffect", 1)
hook("SetDoodadAnimationRect", 1)
hook("GroupEnumUnitsInRect", 2)
hook("GroupEnumUnitsInRectCounted", 2)
hook("RegionAddRect", 2)
hook("RegionClearRect", 2)
hook("SetBlightRect", 2)
hook("SetFogStateRect", 3)
hook("CreateFogModifierRect", 3)
end
--[===============================[
• FORCES (PLAYER GROUPS IN GUI) •
--]===============================]
do
---@class force: { [integer]: player }
---@field indexOf { [player]: integer }
local oldForce, mainForce, initForce = CreateForce, nil, nil
initForce = function()
initForce = DoNothing
mainForce = oldForce()
end
CreateForce=function() return {indexOf={}} end
DestroyForce=DoNothing
local oldClear=ForceClear
function ForceClear(force)
if force then
for i,val in ipairs(force) do
force.indexOf[val]=nil
force[i]=nil
end
end
end
do
local oldCripple = CripplePlayer
local oldAdd=ForceAddPlayer
CripplePlayer = function(player,force,flag)
if player and force then
initForce()
for _,val in ipairs(force) do
oldAdd(mainForce, val)
end
oldCripple(player, mainForce, flag)
oldClear(mainForce)
end
end
end
function ForceAddPlayer(force, player)
if force and player and not force.indexOf[player] then
local pos = #force+1
force.indexOf[player]=pos
force[pos]=player
end
end
function ForceRemovePlayer(force, player)
local pos = force and player and force.indexOf[player]
if pos then
force.indexOf[player]=nil
local top = #force
if pos ~= top then
force[pos] = force[top]
force.indexOf[force[top]] = pos
end
force[top] = nil
end
end
function BlzForceHasPlayer(force, player)
return force and player and (force.indexOf[player]~=nil)
end
function IsPlayerInForce(player, force)
return BlzForceHasPlayer(force, player)
end
function IsUnitInForce(unit, force)
return BlzForceHasPlayer(force, GetOwningPlayer(unit))
end
local enumPlayer
local oldForForce = ForForce
local oldEnumPlayer = GetEnumPlayer
GetEnumPlayer=function() return enumPlayer end
ForForce = function(force, code)
local old = enumPlayer
for _,player in ipairs(force) do
enumPlayer=player
code()
end
enumPlayer=old
end
local function funnelEnum(force)
ForceClear(force)
initForce()
oldForForce(mainForce, function()
ForceAddPlayer(force, oldEnumPlayer())
end)
oldClear(mainForce)
end
local function hookEnum(varStr, pos)
local new=_G[varStr]
if GUI.settings.USE_BOOLEXPRS then
local convert = GUI.tempBoolExpr
local old = new
if pos == 2 then
new = function(f,func,n)
local cache = GUI.tempFilter
old(f,convert(func),n)
GUI.tempFilter = cache
end
else
new = function(f,p,func)
local cache = GUI.tempFilter
old(f,p,convert(func))
GUI.tempFilter = cache
end
end
end
_G[varStr]=function(force, ...)
initForce()
new(mainForce, ...)
funnelEnum(force)
end
end
hookEnum("ForceEnumPlayers", 2)
hookEnum("ForceEnumPlayersCounted", 2)
hookEnum("ForceEnumAllies", 3)
hookEnum("ForceEnumEnemies", 3)
CountPlayersInForceBJ=function(force) return #force end
CountPlayersInForceEnum=nil ---@diagnostic disable-line
GetForceOfPlayer=function(player)
--No longer leaks. There was no reason to dynamically create forces to begin with.
return bj_FORCE_PLAYER[GetPlayerId(player)]
end
end
--Blizzard forgot to add this, but still enabled it for GUI. Therefore, I've extracted and simplified the code from DebugIdInteger2IdString
---@type fun(value: integer): string
function BlzFourCC2S(value)
local result = ""
for _=1,4 do
result = string.char(value % 256) .. result
value = value // 256
end
return result
end
function TriggerRegisterDestDeathInRegionEvent(trig, r)
--Removes the limit on the number of destructables that can be registered.
EnumDestructablesInRect(r, nil, function() TriggerRegisterDeathEvent(trig, GetEnumDestructable()) end)
end
IsUnitAliveBJ=UnitAlive --use the reliable native instead of the life checks
function IsUnitDeadBJ(u) return not UnitAlive(u) end
function SetUnitPropWindowBJ(whichUnit, propWindow)
--Allows the Prop Window to be set to zero to allow unit movement to be suspended.
SetUnitPropWindow(whichUnit, math.rad(propWindow))
end
if GUI.settings.USE_GLOBAL_REMAP then
OnInit(function(import)
import "GlobalRemap"
GlobalRemap("udg_INFINITE_LOOP", function() return -1 end) --a readonly variable for infinite looping in GUI.
end)
end
do
local cache=__jarray()
function GUI.wrapTrigger(whichTrig)
return cache[whichTrig] or rawset(cache, whichTrig, function()if IsTriggerEnabled(whichTrig)and TriggerEvaluate(whichTrig)then TriggerExecute(whichTrig)end end)[whichTrig]
end
end
do
--[[---------------------------------------------------------------------------------------------
RegisterAnyPlayerUnitEvent by Bribe
RegisterAnyPlayerUnitEvent cuts down on handle count for alread-registered events, plus has
the benefit for Lua users to just use function calls.
Adds a third parameter to the RegisterAnyPlayerUnitEvent function: "skip". If true, disables
the specified event, while allowing a single function to run discretely. It also allows (if
Global Variable Remapper is included) GUI to un-register a playerunitevent by setting
udg_RemoveAnyUnitEvent to the trigger they wish to remove.
The "return" value of RegisterAnyPlayerUnitEvent calls the "remove" method. The API, therefore,
has been reduced to just this one function (in addition to the bj override).
-----------------------------------------------------------------------------------------------]]
local fStack,tStack,oldBJ = {},{},TriggerRegisterAnyUnitEventBJ
function RegisterAnyPlayerUnitEvent(event, userFunc, skip)
if skip then
local t = tStack[event]
if t and IsTriggerEnabled(t) then
DisableTrigger(t)
userFunc()
EnableTrigger(t)
else
userFunc()
end
else
local funcs,insertAt=fStack[event],1
if funcs then
insertAt=#funcs+1
if insertAt==1 then EnableTrigger(tStack[event]) end
else
local t=CreateTrigger()
oldBJ(t, event)
tStack[event],funcs = t,{}
fStack[event]=funcs
TriggerAddCondition(t, function() for _,func in ipairs(funcs)do func()end end)
end
funcs[insertAt]=userFunc
return function()
local total=#funcs
for i=1,total do
if funcs[i]==userFunc then
if total==1 then DisableTrigger(tStack[event]) --no more events are registered, disable the event (for now).
elseif total> i then funcs[i]=funcs[total] end --pop just the top index down to this vacant slot so we don't have to down-shift the entire stack.
funcs[total]=nil --remove the top entry.
return true
end
end
end
end
end
local trigFuncs
---@overload fun(trig: trigger, event: playerunitevent): function?
function TriggerRegisterAnyUnitEventBJ(trig, event)
local removeFunc=RegisterAnyPlayerUnitEvent(event, GUI.wrapTrigger(trig))
if GUI.settings.USE_GLOBAL_REMAP then
if not trigFuncs then
trigFuncs=__jarray()
GlobalRemap("udg_RemoveAnyUnitEvent", nil, function(t)
if trigFuncs[t] then
trigFuncs[t]()
trigFuncs[t]=nil
end
end)
end
trigFuncs[trig]=removeFunc
end
return removeFunc
end
end
---Modify to allow requests for negative hero stats, as per request from Tasyen.
---@param whichHero unit
---@param whichStat integer
---@param value integer
function SetHeroStat(whichHero, whichStat, value)
(whichStat==bj_HEROSTAT_STR and SetHeroStr or whichStat==bj_HEROSTAT_AGI and SetHeroAgi or SetHeroInt)(whichHero, value, true)
end
--Another implementation to correct checking if a unit is stunned. This one was also requested by Tasyen, but I made an optimization to only enable the hook when the variable is actually queried.
if GUI.settings.USE_GLOBAL_REMAP then
local stunned = UNIT_TYPE_STUNNED
local old = IsUnitType
local function hook(unit, unitType)
IsUnitType = old
if unitType == stunned then
return GetUnitCurrentOrder(unit) == 851973
end
return old(unit, unitType)
end
GlobalRemap("UNIT_TYPE_STUNNED", function()
IsUnitType = hook
return stunned
end)
end
--The next part of the code is purely optional, as it is intended to optimize rather than add new functionality
CommentString = nil ---@diagnostic disable-line
RegisterDestDeathInRegionEnum = nil ---@diagnostic disable-line
--This next list comes from HerlySQR, and its purpose is to eliminate useless wrapper functions (only where the parameters aligned):
StringIdentity = GetLocalizedString
TriggerRegisterTimerExpireEventBJ = TriggerRegisterTimerExpireEvent
TriggerRegisterDialogEventBJ = TriggerRegisterDialogEvent
TriggerRegisterUpgradeCommandEventBJ = TriggerRegisterUpgradeCommandEvent
RemoveWeatherEffectBJ = RemoveWeatherEffect
DestroyLightningBJ = DestroyLightning
GetLightningColorABJ = GetLightningColorA
GetLightningColorRBJ = GetLightningColorR
GetLightningColorGBJ = GetLightningColorG
GetLightningColorBBJ = GetLightningColorB
SetLightningColorBJ = SetLightningColor
GetAbilityEffectBJ = GetAbilityEffectById
GetAbilitySoundBJ = GetAbilitySoundById
ResetTerrainFogBJ = ResetTerrainFog
SetSoundDistanceCutoffBJ = SetSoundDistanceCutoff
SetSoundPitchBJ = SetSoundPitch
AttachSoundToUnitBJ = AttachSoundToUnit
KillSoundWhenDoneBJ = KillSoundWhenDone
PlayThematicMusicBJ = PlayThematicMusic
EndThematicMusicBJ = EndThematicMusic
StopMusicBJ = StopMusic
ResumeMusicBJ = ResumeMusic
VolumeGroupResetImmediateBJ = VolumeGroupReset
WaitForSoundBJ = TriggerWaitForSound
ClearMapMusicBJ = ClearMapMusic
DestroyEffectBJ = DestroyEffect
GetItemLifeBJ = GetWidgetLife -- This was just to type casting
SetItemLifeBJ = SetWidgetLife -- This was just to type casting
GetLearnedSkillBJ = GetLearnedSkill
UnitDropItemPointBJ = UnitDropItemPoint
UnitDropItemTargetBJ = UnitDropItemTarget
UnitUseItemDestructable = UnitUseItemTarget -- This was just to type casting
UnitInventorySizeBJ = UnitInventorySize
SetItemInvulnerableBJ = SetItemInvulnerable
SetItemDropOnDeathBJ = SetItemDropOnDeath
SetItemDroppableBJ = SetItemDroppable
SetItemPlayerBJ = SetItemPlayer
ChooseRandomItemBJ = ChooseRandomItem
ChooseRandomNPBuildingBJ = ChooseRandomNPBuilding
ChooseRandomCreepBJ = ChooseRandomCreep
String2UnitIdBJ = UnitId -- I think they just wanted a better name
GetIssuedOrderIdBJ = GetIssuedOrderId
GetKillingUnitBJ = GetKillingUnit
IsUnitHiddenBJ = IsUnitHidden
IssueTrainOrderByIdBJ = IssueImmediateOrderById -- I think they just wanted a better name
IssueUpgradeOrderByIdBJ = IssueImmediateOrderById -- I think they just wanted a better name
GetAttackedUnitBJ = GetTriggerUnit -- I think they just wanted a better name
SetUnitFlyHeightBJ = SetUnitFlyHeight
SetUnitTurnSpeedBJ = SetUnitTurnSpeed
GetUnitDefaultPropWindowBJ = GetUnitDefaultPropWindow
SetUnitBlendTimeBJ = SetUnitBlendTime
SetUnitAcquireRangeBJ = SetUnitAcquireRange
UnitSetCanSleepBJ = UnitAddSleep
UnitCanSleepBJ = UnitCanSleep
UnitWakeUpBJ = UnitWakeUp
UnitIsSleepingBJ = UnitIsSleeping
IsUnitPausedBJ = IsUnitPaused
SetUnitExplodedBJ = SetUnitExploded
GetTransportUnitBJ = GetTransportUnit
GetLoadedUnitBJ = GetLoadedUnit
IsUnitInTransportBJ = IsUnitInTransport
IsUnitLoadedBJ = IsUnitLoaded
IsUnitIllusionBJ = IsUnitIllusion
SetDestructableInvulnerableBJ = SetDestructableInvulnerable
IsDestructableInvulnerableBJ = IsDestructableInvulnerable
SetDestructableMaxLifeBJ = SetDestructableMaxLife
WaygateIsActiveBJ = WaygateIsActive
QueueUnitAnimationBJ = QueueUnitAnimation
SetDestructableAnimationBJ = SetDestructableAnimation
QueueDestructableAnimationBJ = QueueDestructableAnimation
DialogSetMessageBJ = DialogSetMessage
DialogClearBJ = DialogClear
GetClickedButtonBJ = GetClickedButton
GetClickedDialogBJ = GetClickedDialog
DestroyQuestBJ = DestroyQuest
QuestSetTitleBJ = QuestSetTitle
QuestSetDescriptionBJ = QuestSetDescription
QuestSetCompletedBJ = QuestSetCompleted
QuestSetFailedBJ = QuestSetFailed
QuestSetDiscoveredBJ = QuestSetDiscovered
QuestItemSetDescriptionBJ = QuestItemSetDescription
QuestItemSetCompletedBJ = QuestItemSetCompleted
DestroyDefeatConditionBJ = DestroyDefeatCondition
DefeatConditionSetDescriptionBJ = DefeatConditionSetDescription
FlashQuestDialogButtonBJ = FlashQuestDialogButton
DestroyTimerBJ = DestroyTimer
DestroyTimerDialogBJ = DestroyTimerDialog
TimerDialogSetTitleBJ = TimerDialogSetTitle
TimerDialogSetSpeedBJ = TimerDialogSetSpeed
TimerDialogDisplayBJ = TimerDialogDisplay
LeaderboardSetStyleBJ = LeaderboardSetStyle
LeaderboardGetItemCountBJ = LeaderboardGetItemCount
LeaderboardHasPlayerItemBJ = LeaderboardHasPlayerItem
DestroyLeaderboardBJ = DestroyLeaderboard
LeaderboardDisplayBJ = LeaderboardDisplay
LeaderboardSortItemsByPlayerBJ = LeaderboardSortItemsByPlayer
LeaderboardSortItemsByLabelBJ = LeaderboardSortItemsByLabel
PlayerGetLeaderboardBJ = PlayerGetLeaderboard
DestroyMultiboardBJ = DestroyMultiboard
SetTextTagPosUnitBJ = SetTextTagPosUnit
SetTextTagSuspendedBJ = SetTextTagSuspended
SetTextTagPermanentBJ = SetTextTagPermanent
SetTextTagAgeBJ = SetTextTagAge
SetTextTagLifespanBJ = SetTextTagLifespan
SetTextTagFadepointBJ = SetTextTagFadepoint
DestroyTextTagBJ = DestroyTextTag
ForceCinematicSubtitlesBJ = ForceCinematicSubtitles
DisplayCineFilterBJ = DisplayCineFilter
SaveGameCacheBJ = SaveGameCache
FlushGameCacheBJ = FlushGameCache
SaveGameCheckPointBJ = SaveGameCheckpoint
LoadGameBJ = LoadGame
RenameSaveDirectoryBJ = RenameSaveDirectory
RemoveSaveDirectoryBJ = RemoveSaveDirectory
CopySaveGameBJ = CopySaveGame
IssueTargetOrderBJ = IssueTargetOrder
IssueTargetDestructableOrder = IssueTargetOrder -- This was just to type casting
IssueTargetItemOrder = IssueTargetOrder -- This was just to type casting
IssueImmediateOrderBJ = IssueImmediateOrder
GroupTargetOrderBJ = GroupTargetOrder
GroupImmediateOrderBJ = GroupImmediateOrder
GroupTargetDestructableOrder = GroupTargetOrder -- This was just to type casting
GroupTargetItemOrder = GroupTargetOrder -- This was just to type casting
GetDyingDestructable = GetTriggerDestructable -- I think they just wanted a better name
GetAbilityName = GetObjectName -- I think they just wanted a better name
if Debug then Debug.endFile() end
OnInit("PreciseWait", function(require) --https://www.hiveworkshop.com/threads/total-initialization.317099/
local hook = require.optionally "AddHook" --https://www.hiveworkshop.com/threads/hook.339153
local remap = require.optionally "GlobalRemap" --https://www.hiveworkshop.com/threads/global-variable-remapper-the-future-of-gui.339308/
--Precise Wait v1.5.0.1
--This changes the default functionality of TriggerAddAction, PolledWait
--and (because they don't work with manual coroutines) TriggerSleepAction and SyncSelections.
local _ACTION_PRIORITY = 1 --Specify the hook priority for hooking TriggerAddAction (higher numbers run earlier in the sequence).
local _WAIT_PRIORITY = -2 --The hook priority for TriggerSleepAction/PolledWait
local function wait(duration)
local thread = coroutine.running()
if thread then
local t = CreateTimer()
TimerStart(t, duration, false, function()
DestroyTimer(t)
coroutine.resume(thread)
end)
coroutine.yield(thread)
end
end
if remap then
--This enables GUI to access WaitIndex as a "local" index for their arrays, which allows
--the simplest fully-instanciable data attachment in WarCraft 3's GUI history. However,
--using it as an array index will cause memory leaks over time, unless you also install
--Lua-Infused GUI: https://www.hiveworkshop.com/threads/lua-infused-gui-automatic-group-location-rect-and-force-leak-prevention.317084/
remap("udg_WaitIndex", coroutine.running)
end
if not hook then
hook = function(varName, userFunc)
local old = rawget(_G, varName)
rawset(_G, varName, userFunc)
return old
end
end
hook("PolledWait", wait, _WAIT_PRIORITY)
hook("TriggerSleepAction", wait, _WAIT_PRIORITY)
local oldSync
oldSync = hook("SyncSelections",
function()
local thread = coroutine.running()
if thread then
function SyncSelectionsHelper() --this function gets re-declared each time, so calling it via ExecuteFunc will still reference the correct thread.
oldSync()
coroutine.resume(thread)
end
ExecuteFunc("SyncSelectionsHelper")
coroutine.yield(thread)
end
end)
local oldAdd
oldAdd = hook("TriggerAddAction", function(trig, func)
--Return a function that will actually be added as the triggeraction, which itself wraps the actual function in a coroutine.
return oldAdd(trig, function() coroutine.resume(coroutine.create(func)) end)
end, _ACTION_PRIORITY)
end)
--[[
Event v2.1
Event is built for GUI support, event linking via coroutines, simple events (e.g. Heal Event),
binary events (like Unit Indexer) or complex event systems like Spell Event, Damage Engine and Unit Event.
Event.create
============
Create an event that is recursion-proof by default, with easy syntax for GUI support.
--In its most basic form:
Event.create "MyEvent" -> Create your event.
Event.MyEvent.register(myFunc) -> Global API for the user to call to register their callback function.
Event.MyEvent() -> call this to execute the event (inherits this functionality from Hook).
--If GUI has a variable by the same name, it hooks it internally (automating the udg_ portion) to allow this to work:
Game - Value of MyEvent becomes Equal to 0.00
NOTE - the value that MyEvent compares to is its priority in the event sequence, so events with higher numbers run first.
--Enhanced event execution:
Event.MyEvent.execute(extraValue:any, eventSucceeded:boolean, ...)
- Run the event with special data attached (e.g. Spell Event uses the ability ID, Damage Engine uses limitops)
- In most cases, eventSucceeded should be "true". However (for example) Attack Engine -> Damage Engine data transmission will use "false" to cover "missed" events.
- Neither extraValue nor eventSucceeded are propogated as parameters to the callback functions.
--Enhanced event registration:
Event.SpellEffect.await(function() print "Medivh's Raven Form was used" end), 'Amrf', true)
- This is an example of how Spell Event uses the ability ID to distinguish a special callback to this function.
- The second parameter specifies the value that should be matched for the event to run.
- The third value must be "true" if the event should be static (rather than called only once)
--WaitForEvent functionality:
Event.OnUnitIndexed.register(function()
print"Unit Indexed" --runs for any unit.
Event.OnUnitRemoval.await(function()
print "Unit Deindexed" --runs only for the specific unit from the OnUnitIndexed event, and then automatically removes this one-off event once it runs.
end)
end)
]]
OnInit(function(require) --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Total_Initialization.lua
local hook = require "AddHook" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Hook.lua
local remap = require.lazily "GlobalRemap" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Global_Variable_Remapper.lua
local sleep = require.lazily "PreciseWait" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/PreciseWait.lua
local wrapTrigger = require.lazily "GUI.wrapTrigger" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Lua-Infused-GUI.lua
local _PRIORITY = 1000 --The hook priority assigned to the event executor.
local allocate, continue
local currentEvent, depth = {}, {}
Event = {
current = currentEvent,
stop = function() continue = false end
}
do
local function addFunc(name, func, priority, hookIndex)
assert(type(func) == "function")
local funcData = { active = true }
funcData.next,
funcData.remove = hook(
hookIndex or name,
function(...)
if continue then
if funcData.active then
currentEvent.funcData = funcData
depth[funcData] = 0
func(...)
end
funcData.next(...)
end
end,
priority, (hookIndex and Event[name].promise) or Event, DoNothing, false
)
return funcData
end
---@param name string -- A unique name for the event. GUI trigger registration will check if "udg_".."ThisEventName" exists, so do not prefix it with udg_.
---@return table
function Event.create(name)
local event = allocate(name)
---Register a function to the event.
---@param userFunc function
---@param priority? number defaults to 0.
---@param userTrig? trigger only exists if was called from TriggerRegisterVariableEvent, and only useful if this function is hooked.
---@param limitOp? limitop same as the above.
---@return table
function event.register(userFunc, priority, userTrig, limitOp)
return addFunc(name, userFunc, priority)
end
---Calls userFunc when the event is run with the specified index.
---@param userFunc function
---@param onValue any Defaults to currentEvent.data. This is the value that needs to match when the event runs.
---@param runOnce? boolean Defaults true. If true, will remove itself after being called the first time.
---@param priority? number defaults to 0
---@return table
function event.await(userFunc, onValue, runOnce, priority)
onValue = onValue or currentEvent.data
runOnce = runOnce ~= false
return addFunc(name,
function(...)
userFunc(...)
if runOnce then
Event.current.funcData.remove()
if event.promise[onValue] == DoNothing then --no further events exist on this, so Hook has defaulted back to DoNothing
event.promise[onValue] = nil
end
end
end,
priority, onValue
)
end
return event --return the event object. Not needed; the user can just access it via Event.MyEventName
end
end
local createHook
do
local realID --Needed for GUI support to correctly detect Set WaitForEvent = SomeEvent.
realID = {
n = 0,
name = {},
create = function(name)
realID.n = realID.n + 1
realID.name[realID.n] = name
return realID.n
end
}
local function testGlobal(udgName) return globals[udgName] end
function createHook(name)
local udgName = "udg_" .. name ---@type string|false
local isGlobal = pcall(testGlobal, udgName)
local destroy
udgName = (isGlobal or _G[udgName]) and udgName
if udgName then --only proceed with this block if this is a GUI-compatible string.
if isGlobal then
globals[udgName] = realID.create(name) --WC3 will complain if this is assigned to a non-numerical value, hence have to generate one.
else
_G[udgName] = name --do this as a failsafe in case the variable exists but didn't get declared in a GUI Variable Event.
end
destroy = select(2,
hook("TriggerRegisterVariableEvent", --PreciseWait is needed if triggers use WaitForEvent/SleepEvent.
function(userTrig, userStr, userOp, priority)
if udgName == userStr then
Event[name].register(
wrapTrigger and wrapTrigger(userTrig) or
function()
if IsTriggerEnabled(userTrig) and TriggerEvaluate(userTrig) then
TriggerExecute(userTrig)
end
end,
priority, false, userTrig, userOp
)
else
return TriggerRegisterVariableEvent.actual(userTrig, userStr, userOp, priority)
end
end
)
)
end
return function()
if destroy then destroy() end
Event[name] = nil
end
end
if remap then
if sleep then
remap("udg_WaitForEvent", nil,
function(whichEvent)
if type(whichEvent) == "number" then
whichEvent = realID.name[whichEvent] --this is a real value (globals.udg_eventName) rather than simply _G.eventName (which stores the string).
end
assert(whichEvent)
local co = coroutine.running()
Event[whichEvent].await(function() coroutine.resume(co) end)
coroutine.yield()
end
)
remap("udg_SleepEvent", nil,
function(duration) --Yields the coroutine while preserving the event index for the user.
local funcData, data = currentEvent.funcData, currentEvent.data
PolledWait(duration)
currentEvent.funcData, currentEvent.data = funcData, data
end
)
end
remap("udg_EventSuccess",
function() return currentEvent.success end,
function(value) currentEvent.success = value end
)
remap("udg_EventOverride", nil, Event.stop)
remap("udg_EventIndex", function() return currentEvent.data end)
remap("udg_EventRecursion", nil, function(maxDepth) currentEvent.funcData.maxDepth = maxDepth end)
end
end
local createExecutor
do
local freeze
freeze = { --this enables the same recursion mitigation as what was introduced in Damage Engine 5
list = {},
apply = function(funcData)
funcData.active = false
table.insert(freeze.list, funcData)
end,
release = function()
if freeze.list[1] then
for _, funcData in ipairs(freeze.list) do
funcData.active = true
end
freeze.list = {}
end
end
}
function createExecutor(next, promise)
local function runEvent(promiseID, success, eventID, ...)
continue = true
currentEvent.data = eventID
currentEvent.success = success
if promise and promiseID then
if promise[promiseID] then
promise[promiseID](eventID, ...) --promises are run before normal events.
end
if promiseID ~= eventID and promise[eventID] then --avoid calling duplicate promises.
promise[eventID](eventID, ...)
end
end
next(eventID, ...)
end
local runQueue
return function(...)
local funcData = currentEvent.funcData
if funcData then --if another event is already running.
runQueue = runQueue or {}
table.insert(runQueue, table.pack(...)) --rather than going truly recursive, queue the event to be ran after the already queued event(s).
depth[funcData] = depth[funcData] + 1
if depth[funcData] > (funcData.maxDepth or 0) then --max recursion has been reached for this function.
freeze.apply(funcData) --Pause it and let it be automatically unpaused at the end of the sequence.
end
else
runEvent(...)
while runQueue do --This works similarly to the recursion processing introduced in Damage Engine 5.
local tempQueue = runQueue
runQueue = nil
for _, args in ipairs(tempQueue) do
runEvent(table.unpack(args, 1, args.n))
end
end
currentEvent.funcData = nil
freeze.release()
end
end
end
end
---@param name string
---@return table
function allocate(name)
assert(type(name) == "string")
assert(not Event[name])
local event
local next = hook(
name,
function(eventIndex, ...)
event.execute(eventIndex, true, eventIndex, ...) --normal Event("MyEvent",...) function call will have the promise ID matched to the event ID, and "success" as true.
end,
_PRIORITY, Event, DoNothing, false
)
event = Event[name]
event.promise = __jarray() --using a jarray allows Lua-Infused GUI to clean up expired promises.
event.execute = createExecutor(next, event.promise)
event.destroy = createHook(name)
return event
end
end)
if Debug then Debug.beginFile "Base64" end
--[[
Base64 v1
Provides functionality to tightly pack data, optimized for the most dense info storage.
API:
Base64.Encoder.create() -> Encoder
- Creates a new encoder instance
Encoder:writeBitString(bitString: integer, bitLength: integer)
- Add bitLength bits to the resulting data
Encoder:buildString() -> string
- Returns a string with the encoded data
Base64.Decoder.create(data: string) -> Decoder
- Creates a decoder instance that will read through the string
Decoder:readBitString(bitLength: integer) -> integer
- Reads the next bitLength bits from the
Base64.Internal
CHARMAP - the default RFC4822 compliant charmap
REVERSE_CHARMAP - the inverse of the default charmap
GenerateCharmap(charset: string, voidInt, setSize, i2ch, ch2i) -> Charmap, ReverseCharmap
- Generates a charmap and its reverse for the given charset
- Can be used to obfuscate the encoding
Optional requirements:
DebugUtils by Eikonium @ https://www.hiveworkshop.com/threads/330758/
Total Initialization by Bribe @ https://www.hiveworkshop.com/threads/317099/
Inspired by:
- 's Base64 @ https://www.hiveworkshop.com/threads/278664/
- Aniki's Base64 & BitBuf @ https://www.hiveworkshop.com/threads/325749/
Updated: 18 Jan 2023
--]]
OnInit("Base64", function()
-- precomputed charmaps
local CHARMAP = {
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
}
local REVERSE_CHARMAP = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
--- Generates a new charset dictionaries
---@param charset string
local function GenerateCharmap(charset, voidInt, setSize, i2ch, ch2i)
i2ch = i2ch or {}
ch2i = ch2i or {}
voidInt = voidInt or 1
setSize = setSize or 255
for i = 1, setSize, 1 do
ch2i[i] = voidInt
end
for i = 1, #charset, 1 do
i2ch[i] = charset:sub(i, i)
ch2i[charset:byte(i, i)] = i
end
return i2ch, ch2i
end
local Internal = {
CHARMAP,
REVERSE_CHARMAP,
GenerateCharmap = GenerateCharmap,
}
--[ ENCODER CLASS ]--
--- Encodes a integer value sequence into a string.
--- Supports arbitrary bit lengths.
---@class Encoder
---@field buffer integer
---@field buffer_len integer
---@field bit_len integer
---@field out table
---@field out_len integer
local Encoder = {}
Encoder.__index = Encoder
function Encoder.create()
return setmetatable({
buffer = 0,
buffer_len = 0,
bit_len = 0,
out = {},
out_len = 0
}, Encoder)
end
--- Must be sure that the integer provided is in the range [0, 64]
---@param e Encoder
---@param octet integer
local function writeOctetUnsafe(e, octet)
e.out_len = e.out_len + 1
e.out[e.out_len] = CHARMAP[octet + 1]
end
---@param e Encoder
---@param buffer integer
local function writeBuffer(e, buffer)
writeOctetUnsafe(e, (buffer & 0x3f))
writeOctetUnsafe(e, (buffer & 0xfc0) >> 6)
writeOctetUnsafe(e, (buffer & 0x3f000) >> 12)
writeOctetUnsafe(e, (buffer & 0xfc0000) >> 18)
end
---@param e Encoder
---@param buffer integer
---@param len integer
local function addToBuffer(e, buffer, len)
if len < 24 then
return buffer, len
end
local r, l = 0, 0
if len > 24 then
l = len - 24
r = buffer & ((1 << l) - 1)
buffer = buffer >> l
end
writeBuffer(e, buffer)
return r, l
end
--- Must be sure that the len is in the range [1,31], and the integer is in [0, 2^len - 1]
---@param bstr integer
---@param len integer
function Encoder:writeBitString(bstr, len)
-- optimised for performance
-- bstr = bstr & (1 << len) - 1) -- clamp extra bits for safety -- isn't necessary for Object64 lib
self.bit_len = self.bit_len + len
local shift = self.buffer_len + len
if shift <= 31 then
self.buffer, self.buffer_len = addToBuffer(self, (self.buffer << len) | bstr, shift)
return
end
-- self.buffer <= 23, len <= 31 -> self.buffer + len == 54 in worst case scenario
local rem = shift - 24
writeBuffer(self, (self.buffer << (24 - self.buffer_len)) | (bstr >> rem))
bstr = bstr & ((1 << rem) - 1)
if rem > 24 then
rem = rem - 24
writeBuffer(self, bstr >> rem)
bstr = bstr & ((1 << rem) - 1)
end
self.buffer, self.buffer_len = addToBuffer(self, bstr, rem)
end
function Encoder:buildString()
if self.buffer > 0 then
writeBuffer(self, self.buffer << (24 - self.buffer_len))
end
return table.concat(self.out)
end
--[ DECODER CLASS ]--
---@class Decoder
---@field buffer integer
---@field buffer_len integer
---@field bit_ptr integer
---@field pointer integer
---@field source string
local Decoder = {}
Decoder.__index = Decoder
function Decoder.create(str)
return setmetatable({
buffer = 0,
buffer_len = 0,
bit_ptr = 0,
pointer = 0,
source = str
}, Decoder)
end
---@param e Decoder
local function readOctetUnsafe(e)
if e.pointer >= #e.source then
return 0
end
local value = REVERSE_CHARMAP[string.byte(e.source, e.pointer + 1, e.pointer + 1)]
e.pointer = e.pointer + 1
return value
end
--- Reads the next 24 bits
---@param e Decoder
local function readBuffer(e)
local r = readOctetUnsafe(e)
r = r | readOctetUnsafe(e) << 6
r = r | readOctetUnsafe(e) << 12
r = r | readOctetUnsafe(e) << 18
return r
end
---@param e Decoder
local function readToBuffer(e, buffer, len)
if len > 7 then
return buffer, len
end
return (buffer << 24) | readBuffer(e), len + 24
end
---@param len integer
function Decoder:readBitString(len)
-- optimised for performance
-- reversing the operations in writeBitString
local shift = len - self.buffer_len
if shift < 0 then
shift = -shift
local value = self.buffer >> shift
self.buffer, self.buffer_len = readToBuffer(self, self.buffer & ((1 << shift) - 1), shift)
return value
end
local value = self.buffer << shift
local bstr = readBuffer(self, 0, 0)
if shift > 24 then
shift = shift - 24
value = value | (bstr << shift)
bstr = readBuffer(self, 0, 0)
end
shift = 24 - shift
value = value | (bstr >> shift)
self.buffer, self.buffer_len = readToBuffer(self, bstr & ((1 << shift) - 1), shift)
return value
end
Base64 = {
Encoder = Encoder,
Decoder = Decoder,
Internal = Internal,
}
local function test()
local e = Encoder.create()
e:writeBitString(0x1234567, 28)
e:writeBitString(0x1234567, 28)
e:writeBitString(0xf, 4)
e:writeBitString(0x1234567, 28)
e:writeBitString(0x1234567, 28)
local d = Decoder.create(e:buildString())
d:readBitString(28)
d:readBitString(28)
d:readBitString(4)
d:readBitString(28)
d:readBitString(28)
end
-- Internal.CHARMAP, Internal.REVERSE_CHARMAP = GenerateCharmap(
-- "yJ3uFjRLC0h5NTSMYHm27WOGUI/Q+9rKiDPdagtsABpxlwoce48nkzXqvE1ZbfV6"
-- )
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "FileIO" end
--[[
FileIO v1
Provides functionality to read and write files, optimized with lua functionality in mind.
API:
FileIO.Save(filename, data)
- Write string data to a file
FileIO.Load(filename) -> string
- Read string data from a file
FileIO.SaveAsserted(filename, data, onFail?) -> bool
- Saves the file and checks that it was saved successfully.
If it fails, passes (filename, data, loadResult) to onFail.
FileIO.enabled : bool
- field that indicates that files can be accessed correctly.
Optional requirements:
DebugUtils by Eikonium @ https://www.hiveworkshop.com/threads/330758/
Total Initialization by Bribe @ https://www.hiveworkshop.com/threads/317099/
Inspired by:
- TriggerHappy's Codeless Save and Load @ https://www.hiveworkshop.com/threads/278664/
- ScrewTheTrees's Codeless Save/Sync concept @ https://www.hiveworkshop.com/threads/325749/
Updated: 18 Jan 2023
--]]
OnInit("FileIO", function()
local RAW_PREFIX = ']]i[['
local RAW_SUFFIX = ']]--[['
local RAW_SIZE = 256 - #RAW_PREFIX - #RAW_SUFFIX
local LOAD_ABILITY = FourCC('ANdc')
local function open(filename)
name = filename
PreloadGenClear()
Preload('")\nendfunction\n//!beginusercode\nlocal p={}local i=function(s)table.insert(p,s)end--[[')
end
local function write(s)
for i = 1, #s, RAW_SIZE do
Preload(RAW_PREFIX .. s:sub(i, i + RAW_SIZE - 1) .. RAW_SUFFIX)
end
end
local function close()
Preload(']]BlzSetAbilityTooltip(' ..
LOAD_ABILITY .. ',table.concat(p),0)\n//!endusercode\nfunction a takes nothing returns nothing\n//')
PreloadGenEnd(name)
name = nil
end
---
---@param filename string
---@param data string
local function savefile(filename, data)
open(filename)
write(data)
close()
end
---@param filename string
---@return string?
local function loadfile(filename)
local s = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, '', 0)
Preloader(filename)
local loaded = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, s, 0)
if loaded == s then
return nil
end
return loaded
end
---@param filename string
---@param data string
---@param onFail function?
---@return boolean
local function saveAsserted(filename, data, onFail)
savefile(filename, data)
local res = loadfile(filename)
if res == data then
return true
end
if onFail then
onFail(filename, data, res)
end
return false
end
local fileIO_enabled = saveAsserted('TestFileIO.pld', 'FileIO is Enabled')
FileIO = {
Save = savefile,
Load = loadfile,
SaveAsserted = saveAsserted,
enabled = fileIO_enabled,
}
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "Object64" end
OnInit("Object64", function(require)
function log2(num)
local i = 0
while num > 0 do
i = i + 1
num = num >> 1
end
return i
end
local Field = {}
--[[ FIELDS: ]]
--[[----------------------]]
--[[ Unsigned Integer ]]
--[[----------------------]]
---@class Field.UInt
---@field name string
---@field bit_len integer
Field.UInt = {}
Field.UInt.__index = Field.UInt
function Field.UInt.new(name, maxValue)
return setmetatable({
name = name,
bit_len = (maxValue and log2(maxValue)) or 31
}, Field.UInt)
end
---@param decoder Decoder
function Field.UInt:decode(decoder)
return decoder:readBitString(self.bit_len)
end
---@param encoder Encoder
function Field.UInt:encode(encoder, value)
encoder:writeBitString(value, self.bit_len)
end
--[[----------------------]]
--[[ Boolean ]]
--[[----------------------]]
---@class Field.Bool
---@field name string
---@field bit_len integer
Field.Bool = {}
Field.Bool.__index = Field.Bool
--- Accepts only dense arrays
---@param name string
function Field.Bool.new(name)
return setmetatable({
name = name,
bit_len = 1
}, Field.Bool)
end
Field.Bool.Instance = Field.Bool.new('bool')
---@param decoder Decoder
function Field.Bool:decode(decoder)
local code = decoder:readBitString(1)
return code ~= 0
end
---@param encoder Encoder
function Field.Bool:encode(encoder, value)
encoder:writeBitString(value * 1, 1)
end
--[[----------------------]]
--[[ Signed Integer ]]
--[[----------------------]]
---@class Field.SignedInt
---@field name string
---@field bit_len integer
Field.SignedInt = {}
Field.SignedInt.__index = Field.SignedInt
function Field.SignedInt.new(name, maxValue)
return setmetatable({
name = name,
bit_len = 1 + ((maxValue and log2(maxValue)) or 31)
}, Field.SignedInt)
end
---@param decoder Decoder
function Field.SignedInt:decode(decoder)
local value = decoder:readBitString(self.bit_len)
if Field.Bool.Instance:decode(decoder) then
return -value
end
return value
end
---@param encoder Encoder
function Field.SignedInt:encode(encoder, value)
if value < 0 then
encoder:writeBitString(-value, self.bit_len)
Field.Bool.Instance:encode(decoder, true)
else
encoder:writeBitString(value, self.bit_len)
Field.Bool.Instance:encode(decoder, false)
end
end
--[[----------------------]]
--[[ Char ]]
--[[----------------------]]
local chars = " !#$%%&'\"()*+,-.0123456789:;=<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}`"
local charset, charset_reverse = Base64.Internal.GenerateCharmap(chars)
-- table.print(charset)
-- table.print(charset_reverse)
---@class Field.Char
---@field name string
---@field bit_len integer
---@field parser ArrayParser
Field.Char = {}
Field.Char.__index = Field.Char
Field.Char.Set = charset
Field.Char.SetRef = charset_reverse
Field.Char.UIntField = Field.UInt.new('char-uint', #chars)
function Field.Char.new(name)
return setmetatable({
name = name,
bit_len = Field.Char.UIntField.bit_len,
}, Field.Char)
end
Field.Char.Instance = Field.Char.new('char')
---@param decoder Decoder
function Field.Char:decode(decoder)
local uint = Field.Char.UIntField:decode(decoder)
return charset[uint + 1] or ' '
end
--- Accepts a byte value, not string
---@param encoder Encoder
function Field.Char:encode(encoder, value)
Field.Char.UIntField:encode(encoder, (charset_reverse[value] or 1) - 1)
end
--[[----------------------]]
--[[ Value Set ]]
--[[----------------------]]
--- Object field info that holds a value from a specific value set.
---@class Field.ValueSet
---@field name string
---@field bit_len integer
---@field ref table
---@field backref table
Field.ValueSet = {}
Field.ValueSet.__index = Field.ValueSet
function Field.ValueSet.createBackRef(object)
backreference = {}
for i, v in ipairs(valueArray) do
backreference[v] = i
end
return backreference
end
--- Accepts only dense arrays
function Field.ValueSet.new(name, valueArray, backreference)
return setmetatable({
name = name,
bit_len = log2(#valueArray),
ref = valueArray,
backref = backreference or Field.ValueSet.createBackRef(valueArray)
}, Field.ValueSet)
end
---@param decoder Decoder
function Field.ValueSet:decode(decoder)
local code = decoder:readBitString(self.bit_len)
return self.ref[code]
end
---@param encoder Encoder
function Field.ValueSet:encode(encoder, value)
local code = self.backref[value]
if not code then
-- return print('Error writing Field ' .. self.name .. ': bad value ' .. value)
end
encoder:writeBitString(code, self.bit_len)
end
-- -- e.g.
-- local InverseBoolField = Field.ValueSet.new('inverse-bool', { true, false })
-- InverseBoolField:encode(..., true) -> 0
-- InverseBoolField:encode(..., false) -> 1
-- InverseBoolField:decode(... => 0) -> true
-- InverseBoolField:decode(... => 1) -> false
--[[----------------------]]
--[[ Array Parser ]]
--[[----------------------]]
--- Array type info specifying value type and maximum capacity.
---@class ArrayParser
---@field minbit_len integer
---@field valueType Field.UInt
---@field length Field.UInt
local ArrayParser = {}
ArrayParser.__index = ArrayParser
function ArrayParser.new(field, maxSize)
local len = Field.UInt.new('length', maxSize)
return setmetatable({
bit_len = len.bit_len + (field.bit_len << len.bit_len),
valueType = field,
length = len
}, ArrayParser)
end
---@param encoder Encoder
function ArrayParser:encode(encoder, array)
-- print('array', self.valueType.name, 'length', #array)
self.length:encode(encoder, #array)
for _, val in ipairs(array) do
-- print('array', self.valueType.name, _, val)
self.valueType:encode(encoder, val)
end
end
---@param decoder Decoder
function ArrayParser:decode(decoder)
local length = self.length:decode(decoder)
-- print('array', self.valueType.name, 'length', length)
local array = {}
for i = 1, length, 1 do
array[i] = self.valueType:decode(decoder)
-- print('array', self.valueType.name, i, array[i])
end
return array
end
--[[----------------------]]
--[[ String ]]
--[[----------------------]]
--- String field info based on the ArrayParser<Char>.
---@class Field.String
---@field name string
---@field bit_len integer
---@field parser ArrayParser
---@field char Field.Char
Field.String = {}
Field.String.__index = Field.String
function Field.String.new(name, maxLen)
local parser = ArrayParser.new(Field.Char.Instance, maxLen)
return setmetatable({
name = name,
bit_len = parser.bit_len,
parser = parser,
}, Field.String)
end
Field.String.Instance = Field.String.new('string-instance')
---@param decoder Decoder
function Field.String:decode(decoder)
local array = self.parser:decode(decoder)
return table.concat(array)
end
---@param encoder Encoder
---@param value string
function Field.String:encode(encoder, value)
-- print('string len', value, #value)
self.parser.length:encode(encoder, #value)
for i = 1, #value, 1 do
-- print('str', value:sub(i, i))
Field.Char.Instance:encode(encoder, value:byte(i, i))
end
end
--[[----------------------]]
--[[ Object ]]
--[[----------------------]]
--- Object field that holds another object.
--- Requires a parser to return the value.
---@class Field.Object
---@field name string
---@field bit_len integer
---@field parser ObjectParser
Field.Object = {}
Field.Object.__index = Field.Object
--- Accepts only a completed parser
function Field.Object.new(name, parser)
return setmetatable({
name = name,
bit_len = parser.bit_len,
parser = parser,
}, Field.Object)
end
---@param decoder Decoder
function Field.Object:decode(decoder)
return self.parser:decode(decoder)
end
---@param encoder Encoder
function Field.Object:encode(encoder, value)
self.parser:encode(encoder, value)
end
--[[----------------------]]
--[[ Object Parser ]]
--[[----------------------]]
--- Object type info specifying what fields to serialize.
---@class ObjectParser
---@field bit_len integer
---@field param table
---@field fieldQueue table
---@field constructor function
---@field postprocessor function
local ObjectParser = {}
ObjectParser.__index = ObjectParser
---@param fieldArray table? Field list
---@param ctor function? Function that creates the object when decoding
---@param postproc function? Function that creates the object when decoding
function ObjectParser.new(fieldArray, ctor, postproc)
local params, queue = {}, nil
if fieldArray then
for _, v in ipairs(fieldArray) do
params[v.name] = v
end
queue = fieldArray
else
queue = {}
end
return setmetatable({
bit_len = 0,
param = params,
fieldQueue = queue,
constructor = ctor or function() return {} end,
postprocessor = postproc or function(x) return x end
}, ObjectParser)
end
function ObjectParser:addField(field)
self.param[field.name] = field
self.fieldQueue[#self.fieldQueue + 1] = field
self.bit_len = self.bit_len + field.bit_len
field.order = #self.fieldQueue
end
function ObjectParser:addFields(...)
local input = table.pack(...)
for i = 1, input.n do
self:addField(input[i])
end
end
---@param encoder Encoder
function ObjectParser:encode(encoder, object)
for _, def in ipairs(self.fieldQueue) do
local value = object[def.name]
-- print('obj[', _, def.name, ']', value)
def:encode(encoder, value)
end
end
---@param decoder Decoder
function ObjectParser:decode(decoder)
local object = self.constructor()
for _, def in ipairs(self.fieldQueue) do
object[def.name] = def:decode(decoder)
-- print('obj[', _, def.name, ']', object[def.name])
end
return self.postprocessor(object)
end
--[[----------------------]]
--[[ Object Switch Parser ]]
--[[----------------------]]
--- Conditional type info that serializes the object into different types
--- depending on a single-type argument.
---@class SwitchParser
---@field argType table
---@field argCreator function
---@field typeDecider function
local SwitchParser = {}
SwitchParser.__index = SwitchParser
---@param argType table The type info of the argument object
---@param argCreator function Function to create the argument from any object
---@param typeDecider function Function to decide the object type by the argument
function SwitchParser.new(argType, argCreator, typeDecider)
return setmetatable({
bit_len = 0,
argType = argType,
argCreator = argCreator,
typeDecider = typeDecider,
}, SwitchParser)
end
---@param encoder Encoder
function SwitchParser:encode(encoder, object)
local arg = self.argCreator(object)
self.argType:encode(encoder, arg)
self.typeDecider(arg):encode(encoder, object)
end
---@param decoder Decoder
function SwitchParser:decode(decoder)
local arg = self.argType:decode(decoder)
return self.typeDecider(arg):decode(decoder)
end
--[[----------------------]]
--[[ Polymorphic Parser ]]
--[[----------------------]]
local PolymorphicParser = {}
PolymorphicParser.__index = PolymorphicParser
---@param argType table The type info of the argument object
---@param classTypes table Array of class types that contain the
---@param typeParsers table Table that matches class type to class parser
function PolymorphicParser.new(argType, classTypes, typeParsers)
local typeField = Field.ValueSet.new('polymorphic-type', classTypes, typeParsers)
return setmetatable(SwitchParser.new(
valueSet,
function(obj) return getmetatable(obj) end,
function(type) return typeParsers[type] end
), PolymorphicParser)
end
setmetatable(PolymorphicParser, SwitchParser)
--[[----------------------]]
--[[ Final lib ]]
--[[----------------------]]
function Serialize(parser, object)
local encoder = Base64.Encoder.create()
parser:encode(encoder, object)
-- print(encoder.bit_len)
return encoder:buildString()
end
function Deserialize(parser, str)
local decoder = Base64.Decoder.create(str)
return parser:decode(decoder)
end
Object64 = {
ObjectParser = ObjectParser,
ArrayParser = ArrayParser,
Field = Field,
Serialize = Serialize,
Deserialize = Deserialize,
}
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "PlayerState" end
OnInit("PlayerState", function()
local localP = GetLocalPlayer()
local localId = GetPlayerId(localP)
local playing = CreateForce()
local playingCount = 0
local multiplayer = false
local onLeaveEvents = {}
local playingInit = {}
local playerName = {}
local tagId = {}
local function onPlayerLeave(func)
onLeaveEvents[#onLeaveEvents + 1] = func
end
local function onPlayingInit(func)
if playingInit then
playingInit[#playingInit + 1] = func
else
ForForce(playing, function() func(GetEnumPlayer()) end)
end
end
do
local leave_trigger = CreateTrigger()
local function playerLeaved()
local p = GetTriggerPlayer()
for i = 1, #onLeaveEvents, 1 do
onLeaveEvents[i](p)
end
end
OnInit.final(function()
TriggerAddCondition(leave_trigger, Condition(playerLeaved))
for i = 0, bj_MAX_PLAYERS - 1 do
local p = Player(i)
local id = GetPlayerId(p)
if GetPlayerController(p) == MAP_CONTROL_USER
and GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING then
ForceAddPlayer(playing, p)
playingCount = playingCount + 1
TriggerRegisterPlayerEvent(leave_trigger, p, EVENT_PLAYER_LEAVE)
end
--- BNet tag calculation
local name = GetPlayerName(p)
local bnetName, tag = name:match('([^#]+)#(%%d+)')
if bnetName then
playerName[id] = bnetName
tagId[id] = tonumber(tag)
else
playerName[id] = name
tagId[id] = 0
end
for i = 1, #playingInit, 1 do
playingInit[i](p)
end
end
multiplayer = playingCount > 1
playingInit = nil
end)
onPlayerLeave(function(p)
ForceRemovePlayer(playing, p)
playingCount = playingCount - 1
end)
end
do
local CURRENT_VERSION = 1
local MAP_KEY = "rG!B@n"
local SYNC_PREFIX = "PST"
local pState = {}
local function GetSaveLocation()
return ([[roguelike/%%s/Save-%%d.pld]]):format(playerName[localId], tagId[localId])
end
local playerStateValidation = Object64.ObjectParser.new({
Object64.Field.String.new('key', 6),
Object64.Field.UInt.new('save_version', 10000),
Object64.Field.UInt.new('bnetTag', 10000),
Object64.Field.String.new('player', 32),
}, nil, function(header)
header.valid = header.key == MAP_KEY
and header.player == playerName[localId]
and header.bnetTag == tagId[localId]
and header.save_version <= CURRENT_VERSION
-- print(([[Checking header:
-- key = %%s | ref: %%s
-- player = %%s | ref: %%s
-- bnetTag = %%d | ref: %%d
-- save_version = %%d | ref: %%d
-- -> %%s]]):format(
-- header.key, MAP_KEY,
-- header.player, playerName[localId],
-- header.bnetTag, tagId[localId],
-- header.save_version, CURRENT_VERSION,
-- header.valid
-- ))
return header
end)
local playerStateInfo = Object64.ObjectParser.new({
Object64.Field.String.new('key', 6),
Object64.Field.UInt.new('save_version', 10000),
Object64.Field.UInt.new('bnetTag', 10000),
Object64.Field.String.new('player', 32),
Object64.Field.Object.new('data', Object64.ObjectParser.new({
Object64.Field.UInt.new('game_count', 8192),
Object64.Field.UInt.new('lumber'),
Object64.Field.UInt.new('souls', 3),
Object64.Field.UInt.new('longevity', 256),
Object64.Field.UInt.new('merc_dmg', 256),
Object64.Field.UInt.new('merc_hp', 256),
Object64.Field.UInt.new('start_gold', 256),
Object64.Field.UInt.new('start_exp', 256),
})),
})
local function CreatePlayerData()
return {
game_count = 1,
run_count = 0,
longevity = 0,
merc_dmg = 0,
merc_hp = 0,
start_gold = 0,
start_exp = 0,
souls = 0,
}
end
local function OnLoadPlayerState(p, data)
local id = GetPlayerId(p)
-- print(("Received %%s <- %%s"):format(data, playerName[id]))
local newState = Object64.Deserialize(playerStateInfo, data)
local data = newState.data
pState[id].data = data
data.game_count = data.game_count + 1
SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER,
GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER)
+ data.lumber)
data.lumber = nil
print(("Welcome back to your %%dth game, %%s"):format(data.game_count, playerName[id]))
if not multiplayer then
print("You are playing in sigleplayer; your progress will not be saved.")
end
for i = 1, data.souls do
GroupAddUnit(
udg_select_hero_souls,
CreateUnit(p, FourCC("ewsp"),
GetRandomReal(GetRectMinX(gg_rct_selectHero2_Copy), GetRectMaxX(gg_rct_selectHero2_Copy)),
GetRandomReal(GetRectMinY(gg_rct_selectHero2_Copy), GetRectMaxY(gg_rct_selectHero2_Copy)),
bj_UNIT_FACING)
)
end
-- table.print(PlayerState[id])
end
local syncTrigger = CreateTrigger()
TriggerAddCondition(syncTrigger, Condition(function()
OnLoadPlayerState(GetTriggerPlayer(), BlzGetTriggerSyncData())
end))
local function SavePlayerState()
if not multiplayer then return end
local state = pState[localId]
state.key = MAP_KEY
state.data.lumber = GetPlayerState(localP, PLAYER_STATE_RESOURCE_LUMBER)
local data = Object64.Serialize(playerStateInfo, state)
state.key = nil
state.data.lumber = nil
FileIO.Save(GetSaveLocation(), data)
-- print("Your soul shall be remembered for ages...")
-- print(("Saved %%s -> %%s"):format(data, GetSaveLocation()))
end
local function LoadPlayerState(p)
if localP == p then
local data = FileIO.Load(GetSaveLocation())
if not data then return end
-- print(("Sent %%s <- %%s"):format(data, GetSaveLocation()))
local h = Object64.Deserialize(playerStateValidation, data)
if h.valid then
BlzSendSyncData(SYNC_PREFIX, data)
else
-- if h.key ~= MAP_KEY then
-- Debug.throwError(('Invalid PlayerState.'))
-- end
-- if h.save_version > CURRENT_VERSION then
-- Debug.throwError(('Your Current PlayerState was saved on a higher map version.'))
-- end
-- if h.player ~= playerName[localId] then
-- Debug.throwError(('This PlayerState belongs to another player!'))
-- end
-- if h.bnetTag ~= playerName[localId] and h.bnetTag ~= 0 then
-- Debug.throwError(('PlayerState belongs to another battle.net account!'))
-- end
end
end
end
onPlayingInit(function(p)
local id = GetPlayerId(p)
pState[id] = {
save_version = CURRENT_VERSION,
bnetTag = tagId[id],
player = playerName[id],
data = CreatePlayerData()
}
BlzTriggerRegisterPlayerSyncEvent(syncTrigger, p, SYNC_PREFIX, false)
LoadPlayerState(p)
end)
PlayerState = {
Save = SavePlayerState,
-- Load = LoadPlayerState,
}
function PlayerState.OnNewRun()
ForForce(playing, function()
local data = pState[GetPlayerId(GetEnumPlayer())].data
data.run_count = data.run_count + 1
end)
end
GlobalRemapArray("udg_souls_upg_gold",
function(i) return pState[i - 1].data.start_gold or 0 end,
function(i, value) pState[i - 1].data.start_gold = value end
)
GlobalRemapArray("udg_souls_upg_xp",
function(i) return pState[i - 1].data.start_xp or 0 end,
function(i, value) pState[i - 1].data.start_xp = value end
)
GlobalRemapArray("udg_souls_upg_steps",
function(i) return pState[i - 1].data.longevity or 0 end,
function(i, value) pState[i - 1].data.longevity = value end
)
GlobalRemapArray("udg_souls_upg_mercDamage",
function(i) return pState[i - 1].data.merc_dmg or 0 end,
function(i, value) pState[i - 1].data.merc_dmg = value end
)
GlobalRemapArray("udg_souls_upg_mercHP",
function(i) return pState[i - 1].data.merc_hp or 0 end,
function(i, value) pState[i - 1].data.merc_hp = value end
)
GlobalRemapArray("udg_souls_upg_heroes",
function(i) return pState[i - 1].data.souls or 0 end,
function(i, value) pState[i - 1].data.souls = value end
)
end
end)
if Debug then Debug.endFile() end
function DB_Unit_AutoFill()
udg_db_unitsLevel_from[1] = 1
udg_db_unitsLevel_from[2] = 31
udg_db_unitsLevel_from[3] = 58
udg_db_unitsLevel_from[4] = 111
udg_db_unitsLevel_from[5] = 143
udg_db_unitsLevel_from[6] = 177
udg_db_unitsLevel_from[7] = 212
udg_db_unitsLevel_from[8] = 229
udg_db_unitsLevel_from[9] = 248
udg_db_unitsLevel_from[10] = 259
udg_db_unitsLevel_to[1] = 30
udg_db_unitsLevel_to[2] = 57
udg_db_unitsLevel_to[3] = 110
udg_db_unitsLevel_to[4] = 142
udg_db_unitsLevel_to[5] = 176
udg_db_unitsLevel_to[6] = 211
udg_db_unitsLevel_to[7] = 228
udg_db_unitsLevel_to[8] = 247
udg_db_unitsLevel_to[9] = 258
udg_db_unitsLevel_to[10] = 272
udg_db_units[1] = FourCC('nanc')
udg_db_units[2] = FourCC('nanm')
udg_db_units[3] = FourCC('nanb')
udg_db_units[4] = FourCC('nban')
udg_db_units[5] = FourCC('nscb')
udg_db_units[6] = FourCC('ndrf')
udg_db_units[7] = FourCC('nenc')
udg_db_units[8] = FourCC('nspg')
udg_db_units[9] = FourCC('nspr')
udg_db_units[10] = FourCC('nspb')
udg_db_units[11] = FourCC('ngna')
udg_db_units[12] = FourCC('ngno')
udg_db_units[13] = FourCC('nhar')
udg_db_units[14] = FourCC('nhfp')
udg_db_units[15] = FourCC('nkob')
udg_db_units[16] = FourCC('nlpr')
udg_db_units[17] = FourCC('nwiz')
udg_db_units[18] = FourCC('nmcf')
udg_db_units[19] = FourCC('nmrl')
udg_db_units[20] = FourCC('nspd')
udg_db_units[21] = FourCC('nrzs')
udg_db_units[22] = FourCC('nrzt')
udg_db_units[23] = FourCC('nsty')
udg_db_units[24] = FourCC('nsat')
udg_db_units[25] = FourCC('nslm')
udg_db_units[26] = FourCC('nsra')
udg_db_units[27] = FourCC('ntrh')
udg_db_units[28] = FourCC('nvdl')
udg_db_units[29] = FourCC('nska')
udg_db_units[30] = FourCC('ndrj')
udg_db_units[31] = FourCC('nbrg')
udg_db_units[32] = FourCC('ncer')
udg_db_units[33] = FourCC('ncea')
udg_db_units[34] = FourCC('ndtr')
udg_db_units[35] = FourCC('ndtp')
udg_db_units[36] = FourCC('ndrp')
udg_db_units[37] = FourCC('ndrm')
udg_db_units[38] = FourCC('nrel')
udg_db_units[39] = FourCC('nfgu')
udg_db_units[40] = FourCC('nftr')
udg_db_units[41] = FourCC('nfsp')
udg_db_units[42] = FourCC('ngrk')
udg_db_units[43] = FourCC('nitr')
udg_db_units[44] = FourCC('nitp')
udg_db_units[45] = FourCC('nltl')
udg_db_units[46] = FourCC('nltc')
udg_db_units[47] = FourCC('nlpd')
udg_db_units[48] = FourCC('nmbg')
udg_db_units[49] = FourCC('nmrr')
udg_db_units[50] = FourCC('nmpg')
udg_db_units[51] = FourCC('ntrs')
udg_db_units[52] = FourCC('ntka')
udg_db_units[53] = FourCC('ntkf')
udg_db_units[54] = FourCC('nubk')
udg_db_units[55] = FourCC('nwlt')
udg_db_units[56] = FourCC('nwwf')
udg_db_units[57] = FourCC('ndmu')
udg_db_units[58] = FourCC('nanw')
udg_db_units[59] = FourCC('nrog')
udg_db_units[60] = FourCC('nbdm')
udg_db_units[61] = FourCC('nsc2')
udg_db_units[62] = FourCC('ndtt')
udg_db_units[63] = FourCC('ndrw')
udg_db_units[64] = FourCC('nrdk')
udg_db_units[65] = FourCC('nbdr')
udg_db_units[66] = FourCC('nbzw')
udg_db_units[67] = FourCC('ngrw')
udg_db_units[68] = FourCC('nadw')
udg_db_units[69] = FourCC('nnht')
udg_db_units[70] = FourCC('nenp')
udg_db_units[71] = FourCC('npfl')
udg_db_units[72] = FourCC('nftt')
udg_db_units[73] = FourCC('ngh1')
udg_db_units[74] = FourCC('nsgn')
udg_db_units[75] = FourCC('nssp')
udg_db_units[76] = FourCC('ngns')
udg_db_units[77] = FourCC('ngnb')
udg_db_units[78] = FourCC('ngnw')
udg_db_units[79] = FourCC('narg')
udg_db_units[80] = FourCC('nhrw')
udg_db_units[81] = FourCC('nhrr')
udg_db_units[82] = FourCC('nhdc')
udg_db_units[83] = FourCC('nhyh')
udg_db_units[84] = FourCC('nitt')
udg_db_units[85] = FourCC('nkog')
udg_db_units[86] = FourCC('nkot')
udg_db_units[87] = FourCC('nwzr')
udg_db_units[88] = FourCC('nmam')
udg_db_units[89] = FourCC('nmtw')
udg_db_units[90] = FourCC('nmrm')
udg_db_units[91] = FourCC('nmfs')
udg_db_units[92] = FourCC('nnwa')
udg_db_units[93] = FourCC('nnwl')
udg_db_units[94] = FourCC('nogr')
udg_db_units[95] = FourCC('nrzb')
udg_db_units[96] = FourCC('nqbh')
udg_db_units[97] = FourCC('ntrv')
udg_db_units[98] = FourCC('nrvf')
udg_db_units[99] = FourCC('nslh')
udg_db_units[100] = FourCC('nsts')
udg_db_units[101] = FourCC('nsko')
udg_db_units[102] = FourCC('nslf')
udg_db_units[103] = FourCC('nsrh')
udg_db_units[104] = FourCC('ndqn')
udg_db_units[105] = FourCC('ntkh')
udg_db_units[106] = FourCC('nvdw')
udg_db_units[107] = FourCC('nskf')
udg_db_units[108] = FourCC('nskm')
udg_db_units[109] = FourCC('njg1')
udg_db_units[110] = FourCC('nskg')
udg_db_units[111] = FourCC('nane')
udg_db_units[112] = FourCC('nass')
udg_db_units[113] = FourCC('nbda')
udg_db_units[114] = FourCC('ncen')
udg_db_units[115] = FourCC('ncim')
udg_db_units[116] = FourCC('ndtb')
udg_db_units[117] = FourCC('ndth')
udg_db_units[118] = FourCC('ndrh')
udg_db_units[119] = FourCC('nele')
udg_db_units[120] = FourCC('ners')
udg_db_units[121] = FourCC('nfgb')
udg_db_units[122] = FourCC('nftb')
udg_db_units[123] = FourCC('nfsh')
udg_db_units[124] = FourCC('nfrp')
udg_db_units[125] = FourCC('nfrl')
udg_db_units[126] = FourCC('nfrs')
udg_db_units[127] = FourCC('nsgt')
udg_db_units[128] = FourCC('nits')
udg_db_units[129] = FourCC('nith')
udg_db_units[130] = FourCC('nmsn')
udg_db_units[131] = FourCC('nowb')
udg_db_units[132] = FourCC('nplb')
udg_db_units[133] = FourCC('nfpl')
udg_db_units[134] = FourCC('nfps')
udg_db_units[135] = FourCC('nrvs')
udg_db_units[136] = FourCC('ntrt')
udg_db_units[137] = FourCC('ntkw')
udg_db_units[138] = FourCC('ntkt')
udg_db_units[139] = FourCC('nubr')
udg_db_units[140] = FourCC('nwen')
udg_db_units[141] = FourCC('nwlg')
udg_db_units[142] = FourCC('nwwg')
udg_db_units[143] = FourCC('nano')
udg_db_units[144] = FourCC('nenf')
udg_db_units[145] = FourCC('nbdw')
udg_db_units[146] = FourCC('ncks')
udg_db_units[147] = FourCC('nsc3')
udg_db_units[148] = FourCC('ndrd')
udg_db_units[149] = FourCC('nsel')
udg_db_units[150] = FourCC('nepl')
udg_db_units[151] = FourCC('nfel')
udg_db_units[152] = FourCC('nsgh')
udg_db_units[153] = FourCC('ngnv')
udg_db_units[154] = FourCC('nhrh')
udg_db_units[155] = FourCC('nhhr')
udg_db_units[156] = FourCC('ninc')
udg_db_units[157] = FourCC('nkol')
udg_db_units[158] = FourCC('nlds')
udg_db_units[159] = FourCC('nlsn')
udg_db_units[160] = FourCC('nwzg')
udg_db_units[161] = FourCC('nmgw')
udg_db_units[162] = FourCC('nmit')
udg_db_units[163] = FourCC('nnws')
udg_db_units[164] = FourCC('nnwr')
udg_db_units[165] = FourCC('nogm')
udg_db_units[166] = FourCC('nomg')
udg_db_units[167] = FourCC('nrzm')
udg_db_units[168] = FourCC('nsrv')
udg_db_units[169] = FourCC('nslr')
udg_db_units[170] = FourCC('nsqt')
udg_db_units[171] = FourCC('nstl')
udg_db_units[172] = FourCC('nsog')
udg_db_units[173] = FourCC('nsln')
udg_db_units[174] = FourCC('ndqv')
udg_db_units[175] = FourCC('ntks')
udg_db_units[176] = FourCC('nubw')
udg_db_units[177] = FourCC('nbds')
udg_db_units[178] = FourCC('ndtw')
udg_db_units[179] = FourCC('ndrs')
udg_db_units[180] = FourCC('nrdr')
udg_db_units[181] = FourCC('nbdk')
udg_db_units[182] = FourCC('nbzk')
udg_db_units[183] = FourCC('ngdk')
udg_db_units[184] = FourCC('nadk')
udg_db_units[185] = FourCC('nndk')
udg_db_units[186] = FourCC('nerd')
udg_db_units[187] = FourCC('nfor')
udg_db_units[188] = FourCC('nfov')
udg_db_units[189] = FourCC('nftk')
udg_db_units[190] = FourCC('nfrb')
udg_db_units[191] = FourCC('ngh2')
udg_db_units[192] = FourCC('nsbm')
udg_db_units[193] = FourCC('ngst')
udg_db_units[194] = FourCC('nwrg')
udg_db_units[195] = FourCC('nhyd')
udg_db_units[196] = FourCC('nitw')
udg_db_units[197] = FourCC('nthl')
udg_db_units[198] = FourCC('nmrv')
udg_db_units[199] = FourCC('nmmu')
udg_db_units[200] = FourCC('nowe')
udg_db_units[201] = FourCC('nplg')
udg_db_units[202] = FourCC('nfpt')
udg_db_units[203] = FourCC('nrvl')
udg_db_units[204] = FourCC('nsqe')
udg_db_units[205] = FourCC('nsrn')
udg_db_units[206] = FourCC('ndqt')
udg_db_units[207] = FourCC('nvdg')
udg_db_units[208] = FourCC('nwnr')
udg_db_units[209] = FourCC('nwld')
udg_db_units[210] = FourCC('nwwd')
udg_db_units[211] = FourCC('njga')
udg_db_units[212] = FourCC('nbld')
udg_db_units[213] = FourCC('npfm')
udg_db_units[214] = FourCC('nfre')
udg_db_units[215] = FourCC('nfrg')
udg_db_units[216] = FourCC('nhrq')
udg_db_units[217] = FourCC('nehy')
udg_db_units[218] = FourCC('nlkl')
udg_db_units[219] = FourCC('nmsc')
udg_db_units[220] = FourCC('nnwq')
udg_db_units[221] = FourCC('nogl')
udg_db_units[222] = FourCC('nfpc')
udg_db_units[223] = FourCC('nrzg')
udg_db_units[224] = FourCC('nslv')
udg_db_units[225] = FourCC('nsqo')
udg_db_units[226] = FourCC('ntrg')
udg_db_units[227] = FourCC('ntkc')
udg_db_units[228] = FourCC('nwns')
udg_db_units[229] = FourCC('nbdo')
udg_db_units[230] = FourCC('ncnk')
udg_db_units[231] = FourCC('nelb')
udg_db_units[232] = FourCC('nfot')
udg_db_units[233] = FourCC('nfra')
udg_db_units[234] = FourCC('nsgb')
udg_db_units[235] = FourCC('ninm')
udg_db_units[236] = FourCC('nwzd')
udg_db_units[237] = FourCC('nmgr')
udg_db_units[238] = FourCC('nmdr')
udg_db_units[239] = FourCC('nowk')
udg_db_units[240] = FourCC('nfpu')
udg_db_units[241] = FourCC('nfpe')
udg_db_units[242] = FourCC('ndrv')
udg_db_units[243] = FourCC('nrvi')
udg_db_units[244] = FourCC('nsoc')
udg_db_units[245] = FourCC('ndqp')
udg_db_units[246] = FourCC('ninf')
udg_db_units[247] = FourCC('nbal')
udg_db_units[248] = FourCC('nerw')
udg_db_units[249] = FourCC('nggr')
udg_db_units[250] = FourCC('nsgg')
udg_db_units[251] = FourCC('nstw')
udg_db_units[252] = FourCC('nrvd')
udg_db_units[253] = FourCC('nsqa')
udg_db_units[254] = FourCC('nsth')
udg_db_units[255] = FourCC('nsrw')
udg_db_units[256] = FourCC('nvde')
udg_db_units[257] = FourCC('nwna')
udg_db_units[258] = FourCC('njgb')
udg_db_units[259] = FourCC('nrwm')
udg_db_units[260] = FourCC('nbwm')
udg_db_units[261] = FourCC('nbzd')
udg_db_units[262] = FourCC('ngrd')
udg_db_units[263] = FourCC('nadr')
udg_db_units[264] = FourCC('nndr')
udg_db_units[265] = FourCC('nfod')
udg_db_units[266] = FourCC('nahy')
udg_db_units[267] = FourCC('nina')
udg_db_units[268] = FourCC('nmgd')
udg_db_units[269] = FourCC('nlrv')
udg_db_units[270] = FourCC('nsll')
udg_db_units[271] = FourCC('ndqs')
udg_db_units[272] = FourCC('ntrd')
udg_db_unName[1] = "Crystalline arachnid"
udg_db_unName[2] = "Spy arachnid"
udg_db_unName[3] = "Spy arachnid"
udg_db_unName[4] = "Bandit"
udg_db_unName[5] = "Coastal crab"
udg_db_unName[6] = "Dranei-Strazh"
udg_db_unName[7] = "Damed antiquity"
udg_db_unName[8] = "Forest Spider"
udg_db_unName[9] = "Spider"
udg_db_unName[10] = "Black spider"
udg_db_unName[11] = "Gnoll-Brakonier"
udg_db_unName[12] = "Gnome"
udg_db_unName[13] = "Garpy reconnaissance"
udg_db_unName[14] = "The fallen priest"
udg_db_unName[15] = "Kobolt"
udg_db_unName[16] = "Makrura-Cras"
udg_db_unName[17] = "A student of the wizard"
udg_db_unName[18] = "Murlock-bugun"
udg_db_unName[19] = "Murlock-Wallpaper"
udg_db_unName[20] = "Spider"
udg_db_unName[21] = "Iklogriv-intelligence"
udg_db_unName[22] = "Swinobes"
udg_db_unName[23] = "Satyr"
udg_db_unName[24] = "Satir-proof"
udg_db_unName[25] = "Swamp bastard"
udg_db_unName[26] = "Pupil from the Boarding Storm clan"
udg_db_unName[27] = "Clothes of turtles"
udg_db_unName[28] = "The younger demon of the abyss"
udg_db_unName[29] = "Lemo skeleton"
udg_db_unName[30] = "Nodal experiment"
udg_db_unName[31] = "Robber"
udg_db_unName[32] = "Kentavr-warrior"
udg_db_unName[33] = "Centivon is a heart"
udg_db_unName[34] = "Dark troll"
udg_db_unName[35] = "Dark troll - priest of darkness"
udg_db_unName[36] = "Dranei-defender"
udg_db_unName[37] = "Drena-boss"
udg_db_unName[38] = "Rifting elemental"
udg_db_unName[39] = "The guard is bad"
udg_db_unName[40] = "Forest troll - Priest Darkness"
udg_db_unName[41] = "Forest troll"
udg_db_unName[42] = "Clay chap"
udg_db_unName[43] = "Ice troll"
udg_db_unName[44] = "Ice troll - Priest"
udg_db_unName[45] = "Thunderstorm lizard"
udg_db_unName[46] = "Makrura-Well"
udg_db_unName[47] = "Makrura-Prochik"
udg_db_unName[48] = "Murlock-Jabber"
udg_db_unName[49] = "Murlock-hunter"
udg_db_unName[50] = "Plague murkov"
udg_db_unName[51] = "Turtle"
udg_db_unName[52] = "Klykarr-bone"
udg_db_unName[53] = "Klykarr-Soldat"
udg_db_unName[54] = "Eternal dark hunter"
udg_db_unName[55] = "Forest Wolf"
udg_db_unName[56] = "northern Wolf"
udg_db_unName[57] = "Dalaran mutant"
udg_db_unName[58] = "Arachnid-warrior"
udg_db_unName[59] = "Robber"
udg_db_unName[60] = "Blue Dragonide - Scout"
udg_db_unName[61] = "Crab-aliener"
udg_db_unName[62] = "Dark troll - hunter"
udg_db_unName[63] = "Dranei-dying"
udg_db_unName[64] = "Red dragon"
udg_db_unName[65] = "Black Dragon"
udg_db_unName[66] = "Bronze dragon"
udg_db_unName[67] = "Green Dragon"
udg_db_unName[68] = "Blue Dragon"
udg_db_unName[69] = "The dragon of emptiness"
udg_db_unName[70] = "Poisonous Act"
udg_db_unName[71] = "The beast is bad"
udg_db_unName[72] = "Forest troll - hunter"
udg_db_unName[73] = "Ghost"
udg_db_unName[74] = "Sea giant"
udg_db_unName[75] = "Spider-player"
udg_db_unName[76] = "Dwell-killer"
udg_db_unName[77] = "Gnome Gromila"
udg_db_unName[78] = "Gnoll-tumor"
udg_db_unName[79] = "Battle golem"
udg_db_unName[80] = "Harpy-Vedim"
udg_db_unName[81] = "Garpy-robber"
udg_db_unName[82] = "Traitor"
udg_db_unName[83] = "The cubs of the hydra"
udg_db_unName[84] = "Ice troll - hunter"
udg_db_unName[85] = "Koboltheante"
udg_db_unName[86] = "Kobolt traitor"
udg_db_unName[87] = "Forethly magician"
udg_db_unName[88] = "Mammoth"
udg_db_unName[89] = "Murlock - Wolf Warrior"
udg_db_unName[90] = "Murlock-PRIPE"
udg_db_unName[91] = "Murlock-Trudomed"
udg_db_unName[92] = "Non-RUB-warrior"
udg_db_unName[93] = "Non-Rube"
udg_db_unName[94] = "Ogra-warrior"
udg_db_unName[95] = "Iklogriv-Gromila"
udg_db_unName[96] = "Svinobraz-hunter"
udg_db_unName[97] = "Exilestone of the waves"
udg_db_unName[98] = "Flame afterlife"
udg_db_unName[99] = "Salamander cub"
udg_db_unName[100] = "Satir-tenephucheus"
udg_db_unName[101] = "Orc skeleton"
udg_db_unName[102] = "Mucus thrower"
udg_db_unName[103] = "Hermit from the clan of the raging storm"
udg_db_unName[104] = "Sukkub"
udg_db_unName[105] = "Klykarr-Elekar"
udg_db_unName[106] = "Demon of the abyss"
udg_db_unName[107] = "Burning archer"
udg_db_unName[108] = "Skeleton-shooter"
udg_db_unName[109] = "Jungly predator"
udg_db_unName[110] = "Huge skeleton-warrior"
udg_db_unName[111] = "Arachnid-Earth"
udg_db_unName[112] = "Murderer"
udg_db_unName[113] = "Young Blue Dragonide"
udg_db_unName[114] = "Centivon-hunger"
udg_db_unName[115] = "Centivor-hunter"
udg_db_unName[116] = "Dark troll - berserker"
udg_db_unName[117] = "Dark troll - senior priest"
udg_db_unName[118] = "Drenaea-Test"
udg_db_unName[119] = "Angered elemental"
udg_db_unName[120] = "Eredar-Coldun"
udg_db_unName[121] = "Bloody outfit"
udg_db_unName[122] = "Forest troll - berserker"
udg_db_unName[123] = "Forest troll - senior priest"
udg_db_unName[124] = "Spontaneous Panaren"
udg_db_unName[125] = "Furbolg"
udg_db_unName[126] = "Furbolg-Shaman"
udg_db_unName[127] = "Huge spider"
udg_db_unName[128] = "Ice trrill - berserker"
udg_db_unName[129] = "Ice troll - senior priest"
udg_db_unName[130] = "Murlock-Lovets"
udg_db_unName[131] = "Owl"
udg_db_unName[132] = "Polar bear"
udg_db_unName[133] = "White Harbolg"
udg_db_unName[134] = "White Harbolg - Shaman"
udg_db_unName[135] = "Finding end of the cold"
udg_db_unName[136] = "Giant turtle"
udg_db_unName[137] = "Klykarr-warrior"
udg_db_unName[138] = "Klykarr-hunter"
udg_db_unName[139] = "Eternal murderer"
udg_db_unName[140] = "Wendigo"
udg_db_unName[141] = "A huge wolf"
udg_db_unName[142] = "Huge northern wolf"
udg_db_unName[143] = "The leader of the Arachnids"
udg_db_unName[144] = "Thug"
udg_db_unName[145] = "Blue Dragonide - Warrior"
udg_db_unName[146] = "Centivor-marshmallow"
udg_db_unName[147] = "Giant crab"
udg_db_unName[148] = "Dreke-killer"
udg_db_unName[149] = "Sea elemental"
udg_db_unName[150] = "Plague Act"
udg_db_unName[151] = "Sleeps"
udg_db_unName[152] = "Submarine chaser"
udg_db_unName[153] = "Gnoll-supervisor"
udg_db_unName[154] = "Harpy Burethnik"
udg_db_unName[155] = "Heretic"
udg_db_unName[156] = "Hell device"
udg_db_unName[157] = "Kobolt inspection"
udg_db_unName[158] = "Makrura-Glubinnik"
udg_db_unName[159] = "Makrura Crusor"
udg_db_unName[160] = "The magician is an offensive"
udg_db_unName[161] = "Magnataurus-warrior"
udg_db_unName[162] = "Ice mammoth"
udg_db_unName[163] = "King of Spiders"
udg_db_unName[164] = "Non-Rub-Provider"
udg_db_unName[165] = "Ogra-gross"
udg_db_unName[166] = "Ogra-Mag"
udg_db_unName[167] = "Iklogriv-Elekar"
udg_db_unName[168] = "The afterlife of the seas"
udg_db_unName[169] = "Salamadra"
udg_db_unName[170] = "Yeti"
udg_db_unName[171] = "Satir Duskerad"
udg_db_unName[172] = "Skeleton-brob"
udg_db_unName[173] = "Swamp monster"
udg_db_unName[174] = "Obvious tormentor"
udg_db_unName[175] = "Klykarr-Mag"
udg_db_unName[176] = "Eternal dark magician"
udg_db_unName[177] = "Blue Dragonide - Mag"
udg_db_unName[178] = "The leader is dark trolls"
udg_db_unName[179] = "Dranei-Provider"
udg_db_unName[180] = "Red Dragon"
udg_db_unName[181] = "Black dragon"
udg_db_unName[182] = "Bronze Dragon"
udg_db_unName[183] = "Green Dragon"
udg_db_unName[184] = "Blue Dragon"
udg_db_unName[185] = "The dragon of emptiness"
udg_db_unName[186] = "Eredar-Demonologist"
udg_db_unName[187] = "Faceless mockery"
udg_db_unName[188] = "The ruler"
udg_db_unName[189] = "Leader Forest Trolls"
udg_db_unName[190] = "Furbolg-PRIVOPIT"
udg_db_unName[191] = "Ghost"
udg_db_unName[192] = "Mother of the pack"
udg_db_unName[193] = "Stone golem"
udg_db_unName[194] = "Military golem"
udg_db_unName[195] = "Hydra"
udg_db_unName[196] = "The leader of the ice trolls"
udg_db_unName[197] = "Thunder lizard 2"
udg_db_unName[198] = "Murlock-Magor"
udg_db_unName[199] = "Murlock Mutant"
udg_db_unName[200] = "Fierce owl"
udg_db_unName[201] = "Huge White Bear"
udg_db_unName[202] = "White Furbolg - Rastype"
udg_db_unName[203] = "Lightning end of the lightning"
udg_db_unName[204] = "Yeti Stareshin"
udg_db_unName[205] = "Necrololite from the raging storm clan"
udg_db_unName[206] = "Limatory temptress"
udg_db_unName[207] = "The ancient demon of the abyss"
udg_db_unName[208] = "Wendigo-Stareshin"
udg_db_unName[209] = "Fierce Wolf"
udg_db_unName[210] = "Fierce Northern Wolf"
udg_db_unName[211] = "A seasoned jungle predator"
udg_db_unName[212] = "Ataman of the robbers"
udg_db_unName[213] = "The ruin is bad"
udg_db_unName[214] = "Furbolg - Senior Shaman"
udg_db_unName[215] = "Furbolg-defender"
udg_db_unName[216] = "Queen Harpius"
udg_db_unName[217] = "Huge hydra"
udg_db_unName[218] = "Makrura - Vladyka Waves"
udg_db_unName[219] = "Murlock - a dark magician"
udg_db_unName[220] = "Queen of Nerubov"
udg_db_unName[221] = "Ogrov commander"
udg_db_unName[222] = "White Harbolg - Defender"
udg_db_unName[223] = "Leader of Iklogriv"
udg_db_unName[224] = "Vizier Salamander"
udg_db_unName[225] = "Jeta-predictor"
udg_db_unName[226] = "Gigantic turtle"
udg_db_unName[227] = "Leader Klykarrov"
udg_db_unName[228] = "Wendigo-Shaman"
udg_db_unName[229] = "Blue Dragonid- Overseer"
udg_db_unName[230] = "Khan of centaurs"
udg_db_unName[231] = "A distraught elemental"
udg_db_unName[232] = "Faceless horror"
udg_db_unName[233] = "Furbolg-warrior"
udg_db_unName[234] = "Sea giant"
udg_db_unName[235] = "Hell car"
udg_db_unName[236] = "Dark magician"
udg_db_unName[237] = "Magnataurus"
udg_db_unName[238] = "Fierce mammoth"
udg_db_unName[239] = "Owl-berrker"
udg_db_unName[240] = "White Harbolg - Warrior"
udg_db_unName[241] = "White Harbolg - Senior Shaman"
udg_db_unName[242] = "The depths of the depths"
udg_db_unName[243] = "Ice afterlife"
udg_db_unName[244] = "The skeleton of the orc defender"
udg_db_unName[245] = "Virgin of pain"
udg_db_unName[246] = "Infernal giant"
udg_db_unName[247] = "The guardian of horror"
udg_db_unName[248] = "Eredar-Black"
udg_db_unName[249] = "Granite golem"
udg_db_unName[250] = "Siege golem"
udg_db_unName[251] = "Storm lizard"
udg_db_unName[252] = "The end of the death"
udg_db_unName[253] = "Ancient Yeti"
udg_db_unName[254] = "Hell satir"
udg_db_unName[255] = "Black Clans of a raging storm"
udg_db_unName[256] = "The senior demon of the abyss"
udg_db_unName[257] = "Ancient Wendigo"
udg_db_unName[258] = "A furious jungle predator"
udg_db_unName[259] = "Ancient Red Dragon"
udg_db_unName[260] = "Ancient black dragon"
udg_db_unName[261] = "Ancient bronze dragon"
udg_db_unName[262] = "Ancient green dragon"
udg_db_unName[263] = "Ancient Blue Dragon"
udg_db_unName[264] = "Ancient dragon of emptiness"
udg_db_unName[265] = "Faceless Bulletin of Death"
udg_db_unName[266] = "Ancient Hydra"
udg_db_unName[267] = "Infernal giant"
udg_db_unName[268] = "Magnataurus-destroyer"
udg_db_unName[269] = "The highest outlet of the depths"
udg_db_unName[270] = "Caliph Salamander"
udg_db_unName[271] = "Queen of suffering"
udg_db_unName[272] = "Dragon Turtle"
end
function CreateCreepAtLoc( player, unitT, loc, angle, actNumber )
local unit = CreateUnitAtLoc(player, unitT, loc, angle)
BlzSetUnitIntegerFieldBJ(unit, UNIT_IF_LEVEL, GetUnitLevel(unit) + (actNumber-1) * 10)
local gold = math.floor( Dif().goldReward * (6 + (GetUnitLevel(unit) * 4)) )
BlzSetUnitIntegerFieldBJ(unit, UNIT_IF_GOLD_BOUNTY_AWARDED_BASE, gold)
BlzSetUnitIntegerFieldBJ(unit, UNIT_IF_GOLD_BOUNTY_AWARDED_SIDES_PER_DIE, 0)
BlzSetUnitIntegerFieldBJ(unit, UNIT_IF_GOLD_BOUNTY_AWARDED_NUMBER_OF_DICE, 0)
return unit
end
function CreepTakeDamage(dmg)
return dmg / udg_act_monsterStats[udg_act_number]
end
function DB_Chunk_Monsters_AutoFill()
udg_chunk_layer_mobSlots[1] = 1
udg_chunk_layer_mobSlots[2] = 2
udg_chunk_layer_mobSlots[3] = 2
udg_chunk_layer_mobSlots[4] = 3
udg_chunk_layer_mobSlots[5] = 3
udg_chunk_layer_mobSlots[6] = 3
udg_chunk_layer_mobSlots[7] = 3
udg_chunk_layer_mobSlots[8] = 3
udg_chunk_layer_mobSlots[9] = 3
udg_chunk_layer_mobSlots[10] = 3
udg_chunk_layer_mobSlots[11] = 3
udg_chunk_layer_mobSlots[12] = 3
udg_chunk_layer_mobSlots[13] = 3
udg_chunk_layer_mobSlots[14] = 3
udg_chunk_layer_mobSlots[15] = 3
udg_chunk_layer_mobSlots[16] = 3
udg_chunk_layers = 16
SaveIntegerBJ( 1, 1, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 2, 1, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 2, 1, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 3, 1, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 1, 2, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 2, 2, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 2, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 5, 2, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 2, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 5, 2, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 0, 2, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 1, 2, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 1, 3, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 2, 3, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 3, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 6, 3, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 3, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 5, 3, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 3, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 3, 3, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 1, 4, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 3, 4, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 2, 4, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 4, 4, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 4, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 4, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 4, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 2, 4, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 6, 4, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 4, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 0, 4, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 1, 4, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 2, 5, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 4, 5, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 2, 5, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 5, 5, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 5, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 5, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 2, 5, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 4, 5, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 6, 5, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 5, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 5, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 1, 5, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 2, 6, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 4, 6, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 6, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 6, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 6, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 6, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 6, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 5, 6, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 7, 6, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 6, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 6, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 1, 6, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 7, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 5, 7, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 7, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 6, 7, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 7, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 7, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 7, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 5, 7, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 7, 7, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 7, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 7, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 2, 7, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 3, 8, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 5, 8, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 8, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 6, 8, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 8, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 8, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 8, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 5, 8, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 7, 8, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 8, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 8, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 3, 8, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 9, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 9, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 9, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 9, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 9, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 9, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 9, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 9, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 7, 9, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 9, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 9, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 3, 9, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 10, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 10, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 10, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 10, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 10, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 10, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 10, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 10, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 10, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 10, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 10, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 3, 10, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 11, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 11, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 11, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 8, 11, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 11, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 11, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 11, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 11, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 11, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 11, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 1, 11, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 4, 11, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 12, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 12, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 12, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 8, 12, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 12, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 12, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 12, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 8, 12, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 12, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 12, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 2, 12, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 4, 12, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 13, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 13, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 13, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 9, 13, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 13, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 13, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 13, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 8, 13, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 13, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 13, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 13, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 5, 13, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 14, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 14, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 14, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 10, 14, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 14, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 14, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 4, 14, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 9, 14, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 14, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 14, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 14, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 6, 14, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 4, 15, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 6, 15, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 15, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 12, 15, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 5, 15, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 7, 15, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 5, 15, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 12, 15, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 15, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 15, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 15, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 15, 3, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 7, 16, 1, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 16, 1, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 7, 16, 1, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 12, 16, 1, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 7, 16, 2, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 16, 2, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 7, 16, 2, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 12, 16, 2, udg_chunk_layer_mob_countTo )
SaveIntegerBJ( 8, 16, 3, udg_chunk_layer_mob_levelFrom )
SaveIntegerBJ( 10, 16, 3, udg_chunk_layer_mob_levelTo )
SaveIntegerBJ( 3, 16, 3, udg_chunk_layer_mob_countFrom )
SaveIntegerBJ( 7, 16, 3, udg_chunk_layer_mob_countTo )
end
preHeroes = {1, 1, 1, 1}
function choosePreHeroes()
local disable = true
while disable do
preHeroes[1] = math.random(1, udg_select_hero_heroes_count)
preHeroes[2] = math.random(1, udg_select_hero_heroes_count)
preHeroes[3] = math.random(1, udg_select_hero_heroes_count)
preHeroes[4] = math.random(1, udg_select_hero_heroes_count)
if
preHeroes[1] ~= preHeroes[2]
and preHeroes[1] ~= preHeroes[3]
and preHeroes[2] ~= preHeroes[3]
and preHeroes[1] ~= preHeroes[4]
and preHeroes[2] ~= preHeroes[4]
and preHeroes[3] ~= preHeroes[4] then
disable = false
end
end
end
function randHero()
return udg_select_hero_heroes[GetRandomInt(1, udg_select_hero_heroes_count)]
end
function optimizeHeroPresetOne()
local isSelected = false
local heroes = {[1] = randHero(), [2] = randHero(), [3] = randHero(), [4] = randHero() }
while not isSelected do
heroes[1] = randHero()
heroes[2] = randHero()
heroes[3] = randHero()
heroes[4] = randHero()
isSelected = (heroes[1] ~= heroes[2]) and (heroes[1] ~= heroes[3]) and (heroes[2] ~= heroes[3]) and (heroes[1] ~= heroes[4]) and (heroes[2] ~= heroes[4]) and (heroes[3] ~= heroes[4])
end
return heroes
end
function optimizeHeroPreset()
local ar
for x = 1, 16, 4 do
ar = optimizeHeroPresetOne()
udg_opt_preloaded_heroes[x] = ar[1]
udg_opt_preloaded_heroes[x+1] = ar[2]
udg_opt_preloaded_heroes[x+2] = ar[3]
udg_opt_preloaded_heroes[x+3] = ar[4]
x = x + 4
end
end
presetHeroes = {}
isInitPresets = false
function addPresetHero(idHero)
local presetHero = {}
presetHero.id = FourCC( idHero )
presetHero.units = {}
presetHero.items = {}
--table.insert(presetHeroes, presetHero)
presetHeroes[presetHero.id] = presetHero
return presetHero
end
function presetHeroAddUnit(preset, unitId, count)
table.insert(preset.units, {id=FourCC(unitId), count=count})
end
function presetHeroAddItem(preset, itemId, count)
table.insert(preset.items, {id=FourCC(itemId), count=count})
end
function createUnitsForHero(hero, outUnitGroup)
local heroId = GetUnitTypeId(hero)
local preset = presetHeroes[heroId]
local player = GetOwningPlayer(hero)
local x = GetUnitX(hero)
local y = GetUnitY(hero)
for i, unitSlot in ipairs(preset.units) do
for i2 = 1, unitSlot.count do
local un = CreateUnit(player, unitSlot.id, x, y, 0)
if outUnitGroup ~= nil then
GroupAddUnitSimple(un, outUnitGroup)
end
end
end
end
function createItemsForHero(hero)
local heroId = GetUnitTypeId(hero)
local preset = presetHeroes[heroId]
for i, itemSlot in ipairs(preset.items) do
for i2 = 1, itemSlot.count do
--local item = CreateUnit(player, unitSlot.id, x, y, 0)
UnitAddItemByIdSwapped(itemSlot.id, hero)
end
end
end
function initPresetHeroes()
if isInitPresets then return end
isInitPresets = true
local preset
--Hamg
preset = addPresetHero("Hamg")
presetHeroAddUnit(preset, "hfoo", 2)
presetHeroAddUnit(preset, "hsor", 1)
presetHeroAddItem(preset, "bspd", 1)
--Hpal
preset = addPresetHero("Hpal")
presetHeroAddUnit(preset, "hkni", 1)
presetHeroAddUnit(preset, "hmpr", 1)
presetHeroAddItem(preset, "stoc", 1)
--Hmkg
preset = addPresetHero("Hmkg")
presetHeroAddUnit(preset, "hrif", 2)
presetHeroAddItem(preset, "I01G", 1)
--Hblm
preset = addPresetHero("Hblm")
presetHeroAddUnit(preset, "hspt", 2)
presetHeroAddItem(preset, "ofr2", 1)
--Obla
preset = addPresetHero("Obla")
presetHeroAddUnit(preset, "ogru", 1)
presetHeroAddUnit(preset, "ohun", 1)
presetHeroAddItem(preset, "pnvu", 2)
--Otch
preset = addPresetHero("Otch")
presetHeroAddUnit(preset, "otau", 1)
presetHeroAddItem(preset, "penr", 1)
--Ofar
preset = addPresetHero("Ofar")
presetHeroAddUnit(preset, "orai", 2)
presetHeroAddItem(preset, "mnst", 1)
--Oshd
preset = addPresetHero("Oshd")
presetHeroAddUnit(preset, "okod", 1)
presetHeroAddItem(preset, "olig", 1)
--Udea
preset = addPresetHero("Udea")
presetHeroAddUnit(preset, "ucry", 1)
presetHeroAddItem(preset, "I01H", 1)
--Udre
preset = addPresetHero("Udre")
presetHeroAddUnit(preset, "uabo", 1)
presetHeroAddItem(preset, "I01I", 1)
--Ucrl
preset = addPresetHero("Ucrl")
presetHeroAddUnit(preset, "ugho", 2)
presetHeroAddItem(preset, "bspd", 1)
presetHeroAddItem(preset, "penr", 1)
--Ulic
preset = addPresetHero("Ulic")
presetHeroAddUnit(preset, "unec", 2)
presetHeroAddItem(preset, "I01J", 1)
--Ekee
preset = addPresetHero("Ekee")
presetHeroAddUnit(preset, "earc", 1)
presetHeroAddItem(preset, "I01K", 1)
presetHeroAddItem(preset, "pmna", 1)
--Emoo
preset = addPresetHero("Emoo")
presetHeroAddUnit(preset, "esen", 2)
presetHeroAddItem(preset, "spsh", 1)
--Ewar
preset = addPresetHero("Ewar")
presetHeroAddUnit(preset, "edry", 2)
presetHeroAddItem(preset, "I01L", 1)
--Edem
preset = addPresetHero("Edem")
presetHeroAddUnit(preset, "edoc", 1)
presetHeroAddItem(preset, "gcel", 1)
presetHeroAddItem(preset, "sora", 1)
--Hlgr
preset = addPresetHero("Hlgr")
presetHeroAddUnit(preset, "hkni", 2)
presetHeroAddItem(preset, "I01M", 1)
--Nmsr
preset = addPresetHero("Nmsr")
presetHeroAddUnit(preset, "nnrg", 1)
presetHeroAddItem(preset, "ofr2", 1)
--Nbrn
preset = addPresetHero("Nbrn")
presetHeroAddUnit(preset, "uban", 1)
presetHeroAddUnit(preset, "ugho", 1)
presetHeroAddItem(preset, "gobm", 2)
--Nplh
preset = addPresetHero("Nplh")
presetHeroAddUnit(preset, "uobs", 1)
presetHeroAddItem(preset, "hlst", 1)
--Ntin
preset = addPresetHero("Ntin")
presetHeroAddUnit(preset, "otbr", 2)
presetHeroAddItem(preset, "I01N", 1)
--Nngs
preset = addPresetHero("Nngs")
presetHeroAddUnit(preset, "nnsw", 1)
presetHeroAddUnit(preset, "nhyc", 1)
presetHeroAddItem(preset, "I01O", 1)
--Nalc
preset = addPresetHero("Nalc")
presetHeroAddUnit(preset, "oshm", 1)
presetHeroAddUnit(preset, "ogru", 1)
presetHeroAddItem(preset, "I01Q", 1)
--Nfir
preset = addPresetHero("Nfir")
presetHeroAddUnit(preset, "nslr", 1)
presetHeroAddItem(preset, "I01P", 1)
--Npbm
preset = addPresetHero("Npbm")
presetHeroAddUnit(preset, "nfps", 1)
presetHeroAddItem(preset, "hlst", 1)
presetHeroAddItem(preset, "mnst", 1)
--Nbst
preset = addPresetHero("Nbst")
presetHeroAddUnit(preset, "edot", 2)
presetHeroAddItem(preset, "I01R", 1)
--Nbst
preset = addPresetHero("H009")
presetHeroAddUnit(preset, "ngrw", 2)
presetHeroAddItem(preset, "I01U", 1)
end
function GenerateLocationsSet( layer, act )
local locSet = {}
local totalWeight = 0.0
for location = 1, udg_location_count do
local weight = udg_location_weight[location]
local cond1 = udg_location_inRandomChoose[location]
local cond2 = not ((layer ~= 16 or act == 3) and ( udg_location_finishLocation == udg_location_triggerPlaced[location]))
if cond1 and cond2 then
table.insert(locSet, {location, weight})
totalWeight = totalWeight + weight
end
end
return {locSet, totalWeight}
end
function GetRandomLocation( locationSets )
local roll = math.random() * locationSets[2]
local locSet = locationSets[1]
for cell = 1, #locSet do
roll = roll - locSet[cell][2]
if roll <= 0 then return locSet[cell][1] end
end
return locSet[#locSet][1]
end
function randLocation( layer )
local sets = GenerateLocationsSet(layer, udg_act_number)
return GetRandomLocation(sets)
--local isSelected = false
--local locIndex
--while not isSelected do
-- locIndex = GetRandomInt(1, udg_location_count)
-- isSelected = not ((layer ~= 16 or udg_act_number == 3) and ( udg_location_finishLocation == udg_location_triggerPlaced[locIndex]))
--end
--return locIndex
end
if Debug then Debug.beginFile "Quest" end
Quest = {}
function Quest.ProgressMessage(quest_id)
return string.format(
"%%s |cffffcc00%%d|r / |cffffcc00%%d|r",
udg_unFamily_name[udg_assigs_t1_unitFamily[quest_id]],
udg_assigs_t1_curCount[quest_id],
udg_assigs_t1_needCount[quest_id]
)
end
function Quest.Description(quest_id)
local message = Quest.ProgressMessage(quest_id)
local quest_description = string.format(
"%%s|nReward: |cffffcc00%%d|r gold, |cffffcc00%%d|r reputation",
message,
udg_assigs_goldReward[quest_id],
udg_assigs_reputationReward[quest_id]
)
return quest_description, message
end
function Quest.CompleteMessage(quest_id)
return string.format(
"The quest after %%s is completed! Your reward: |cffffcc00%%d|r gold, |cffffcc00%%d|r reputation",
udg_unFamily_name[udg_assigs_t1_unitFamily[quest_id]],
udg_assigs_goldReward[GetForLoopIndexA()],
udg_assigs_reputationReward[GetForLoopIndexA()]
)
end
if Debug then Debug.endFile() end
function GetActualMonstersString( maxTypes, mult )
local str = ""
for i = 1, udg_chunk_actual_monsters, 1 do
if i > maxTypes then
return str
end
if i > 1 then
str = str .. ", |n"
end
str = str .. "x" .. (mult * udg_chunk_actual_monster_count[i]) .. " monsters (L-" .. udg_chunk_actual_monster_level[i] .. ")"
end
return str
end
function WavesTemplateAddWave(template)
local wave = {flows={}, startFunctions=nil, multPower=1}
table.insert(template.waves, wave)
return wave
end
function WaveTemplateGetWave(template, waveNumber)
if #template.waves < waveNumber then
for i = #template.waves+1, waveNumber do
WavesTemplateAddWave(template)
end
end
return template.waves[waveNumber]
end
function WaveTemplateUpdateInfo(template)
for waveNumber = 1, #template.waves do
local wave = WaveTemplateGetWave(template, waveNumber)
local totalWeight = 0
for flowNumber = 1, #wave.flows do
local flow = wave.flows[flowNumber]
if flow.countType == 1 then
totalWeight = totalWeight + flow.powerWeight
end
end
for flowNumber = 1, #wave.flows do
local flow = wave.flows[flowNumber]
if flow.countType == 1 then
flow.powerPercent = flow.powerWeight / totalWeight
end
end
end
end
function WaveTemplateAddFlow(template, waveNumber, monsterDB, powerWeight, multCapacity, sortOffset, name, funcsOnSpawn)
if sortOffset == nil then sortOffset = 0 end
local flow = {monsterDB=monsterDB, countType=1, powerWeight=powerWeight, powerPercent=1.0, multCapacity=multCapacity}
flow.sortOffset = sortOffset
flow.name = name
flow.funcsOnSpawn = funcsOnSpawn
flow.funcsOnDeath = nil
local wave = WaveTemplateGetWave(template, waveNumber)
table.insert(wave.flows, flow)
return flow
end
function WaveTemplateAddFlowUniq(template, waveNumber, unitType, sortOffset, name, funcsOnSpawn)
if sortOffset == nil then sortOffset = 0 end
local flow = {countType=2, unitType=unitType, sortOffset=sortOffset}
flow.name = name
flow.funcsOnSpawn = funcsOnSpawn
flow.funcsOnDeath = nil
local wave = WaveTemplateGetWave(template, waveNumber)
table.insert(wave.flows, flow)
return flow
end
function CreateWavesTemplate()
local wavesTemplate = {waves={}}
wavesTemplate.funcsOnCompleted = nil
return wavesTemplate
end
function WaveFlowCreateUnit(flowTemplate, player, position, target)
local unitType = nil
if flowTemplate.countType == 1 then
unitType = flowTemplate.monsterDB.unitType
elseif flowTemplate.countType == 2 then
unitType = flowTemplate.unitType
else
return nil
end
local unit = CreateUnit(player, unitType, position.x, position.y, 0)
if flowTemplate.name ~= nil then
if IsUnitType(unit, UNIT_TYPE_HERO) then
BlzSetHeroProperName(unit, flowTemplate.name)
else
BlzSetUnitName(unit, flowTemplate.name)
end
end
if flowTemplate.funcsOnSpawn ~= nil then
for i, func in ipairs(flowTemplate.funcsOnSpawn) do
func(unit)
end
end
RemoveGuardPosition(unit)
return unit
end
function WaveProcessCreateMobCell(flowProcess)
local mobCell = {units={}, flowProcess=flowProcess}
return mobCell
end
function WaveProcessMobCellIsDead(mobCell)
return #mobCell.aliveMobs == 0
end
function WaveProcessGetNextPosition(waveProcess)
local pos = waveProcess.outPositions[waveProcess.curPosIndex]
waveProcess.curPosIndex = waveProcess.curPosIndex + 1
if waveProcess.curPosIndex > #waveProcess.outPositions then waveProcess.curPosIndex = 1 end
return pos
end
function WaveProcessSpawnNextQueue(waveProcess)
local queue = waveProcess.queueFlowsSpawn
if #queue == 0 then return false end
local flowProcess = queue[1]
table.remove(waveProcess.queueFlowsSpawn, 1)
local position = WaveProcessGetNextPosition(waveProcess)
local unit = WaveFlowCreateUnit(flowProcess.flowTemplate, waveProcess.player, position, waveProcess.target)
local mobCell = WaveProcessCreateMobCell(flowProcess)
table.insert(mobCell.units, unit)
table.insert(waveProcess.mobCells, mobCell)
flowProcess.inBattle = flowProcess.inBattle + 1
flowProcess.inQueue = flowProcess.inQueue - 1
local x, y = GetUnitX(unit), GetUnitY(unit)
SetUnitPosition(unit, waveProcess.target.x, waveProcess.target.y)
SetUnitX(unit, x)
SetUnitY(unit, y)
IssuePointOrderLoc(unit, "attack", waveProcess.targetLocation)
return mobCell
end
function FlowProcessAddQueue(flowProcess)
if flowProcess.reserv <= 0 then return false end
flowProcess.reserv = flowProcess.reserv - 1
flowProcess.inQueue = flowProcess.inQueue + 1
local waveProcess = flowProcess.waveProcess
table.insert(waveProcess.queueFlowsSpawn, flowProcess)
return true
end
function WaveProcessIsCompleted(waveProc)
for i, flowProc in ipairs(waveProc.flowProcesses) do
if flowProc.inBattle > 0 or flowProc.inQueue > 0 or flowProc.reserv > 0 then
return false
end
end
return true
end
function WaveProcessClear(waveProcess)
local mobCells = waveProcess.mobCells
for i, mobCell in ipairs(mobCells) do
for i2, slotUnit in ipairs(mobCell.units) do
if unit ~= slotUnit then RemoveUnit(slotUnit) end
end
end
end
function CreateWaveProcess(waveTemplate, player, power, reservePower, outPositions, target)
local waveProcess = {}
waveProcess.waveTemplate = waveTemplate
waveProcess.flowProcesses = {}
waveProcess.mobCells = {} -- [MobCell]
waveProcess.queueFlowsSpawn = {} -- [flowProcess]
waveProcess.outPositions = outPositions
waveProcess.curPosIndex = 1
waveProcess.target = target
waveProcess.targetLocation = Location(target.x, target.y)
waveProcess.player = player
local arrQueue = {}
for i, flowTemplate in ipairs(waveTemplate.flows) do
local flowProcess = {}
table.insert(waveProcess.flowProcesses, flowProcess)
flowProcess.waveProcess = waveProcess
flowProcess.flowTemplate = flowTemplate
flowProcess.inBattle = 0
flowProcess.inQueue = 0
flowProcess.maxInBattle = 1
flowProcess.initalCount = 1
flowProcess.reserv = 1
if flowTemplate.monsterDB ~= nil then
local tempPower = waveProcess.waveTemplate.multPower
local unitPower = flowTemplate.monsterDB:GetPower()
local squadPower = power * tempPower * flowTemplate.powerPercent
local count = math.floor(0.5 + squadPower / unitPower)
if count < 1 then count = 1 end
flowProcess.maxInBattle = count
local squadReservePower = reservePower * tempPower * flowTemplate.powerPercent
count = math.floor(0.5 + squadReservePower / unitPower)
if count < 1 then count = 1 end
flowProcess.reserv = count
end
flowProcess.initalCount = flowProcess.reserv
local m = flowProcess.maxInBattle
local s = flowTemplate.sortOffset
for k = 1, m do
table.insert(arrQueue, {f=flowProcess, v= s + k/(m+1)})
--FlowProcessAddQueue(flowProcess)
end
end
table.sort( arrQueue, function(ob1, ob2) return ob1.v < ob2.v end )
for i, ob in ipairs(arrQueue) do
FlowProcessAddQueue(ob.f)
end
return waveProcess
end
function Unit2mobCell(unit, waveProcess)
local mobCells = waveProcess.mobCells
for i, mobCell in ipairs(mobCells) do
for i2, slotUnit in ipairs(mobCell.units) do
if unit == slotUnit then return mobCell, i, i2 end
end
end
return nil, 0, 0
end
function WaveProcessDeadUnit( waveProcess, unit)
local mobCell, cellIndex, unitIndex = Unit2mobCell(unit, waveProcess)
if mobCell == nil then return false end
table.remove(mobCell.units, unitIndex)
if #mobCell.units == 0 then
table.remove(waveProcess.mobCells, cellIndex)
local flowProcess = mobCell.flowProcess
local funcs = flowProcess.flowTemplate.funcsOnDeath
if funcs ~= nil then
for i, func in ipairs(funcs) do
func(unit)
end
end
flowProcess.inBattle = flowProcess.inBattle - 1
if flowProcess.reserv > 0 then
FlowProcessAddQueue(flowProcess)
end
end
return true
end
function WaveProcessSummonUnit( waveProcess, summoning, summoned)
local mobCell, cellIndex, unitIndex = Unit2mobCell(summoning, waveProcess)
if mobCell == nil then return false end
table.insert(mobCell.units, summoned)
return true
end
function WaveProcessUnitChangePlayer( waveProcess, unit, prevPlayer, newPlayer)
if prevPlayer ~= waveProcess.player then return false end
return WaveProcessDeadUnit(waveProcess, unit)
end
_allWavesProcesses = nil
function AllWavesProcesses()
if _allWavesProcesses == nil then _allWavesProcesses = {} end
return _allWavesProcesses
end
function CreateWavesProcess(wavesTemplate, player, curPower, limPower, positions, target)
local wavesProcess = {}
wavesProcess.wavesTemplate = wavesTemplate
wavesProcess.player = player
wavesProcess.positions = positions
wavesProcess.target = target
wavesProcess.curPower = curPower
wavesProcess.limPower = limPower
wavesProcess.currentWaveIndex = 1
wavesProcess.currentWaveTemplate = wavesProcess.wavesTemplate.waves[1]
wavesProcess.currentWaveProcess = nil
wavesProcess.isPause = false
wavesProcess.isClear = false
wavesProcess.autoDestorySelf = true
table.insert(AllWavesProcesses(), wavesProcess)
return wavesProcess
end
function DestroyWavesProcess(wavesTemplate)
local t = AllWavesProcesses()
for i, value in ipairs(t) do
if value == wavesTemplate then
table.remove(t, i)
if wavesTemplate.currentWaveProcess ~= nil then
WaveProcessClear(wavesTemplate.currentWaveProcess)
end
return true
end
end
end
function DestroyAllWavesProcess()
local t = AllWavesProcesses()
for i, value in ipairs(t) do
if value.currentWaveProcess ~= nil then
WaveProcessClear(value.currentWaveProcess)
end
end
local count = #t
for i=1, count do t[i]=nil end
end
function CompletedWavesProcess(wavesProc)
local funcs = wavesProc.wavesTemplate.funcsOnCompleted
if funcs ~= nil then
for i, func in ipairs(funcs) do
func(wavesProc)
end
end
end
function UpdateWavesProcess(wavesProc)
if wavesProc.isClear and wavesProc.autoDestorySelf then
DestroyWavesProcess(wavesProc)
return false
end
if wavesProc.isPause or wavesProc.isClear then
return false
end
if wavesProc.currentWaveProcess == nil then
local waveTemp = wavesProc.currentWaveTemplate
local player = wavesProc.player
local pow1 = wavesProc.curPower
local pow2 = wavesProc.limPower
local outPos = wavesProc.positions
local target = wavesProc.target
local waveProc = CreateWaveProcess(waveTemp, player, pow1, pow2, outPos, target)
wavesProc.currentWaveProcess = waveProc
if waveTemp.startFunctions ~= nil then
for i, func in ipairs(waveTemp.startFunctions) do
func()
end
end
end
if wavesProc.currentWaveProcess ~= nil then
WaveProcessSpawnNextQueue( wavesProc.currentWaveProcess )
if WaveProcessIsCompleted(wavesProc.currentWaveProcess) then
wavesProc.currentWaveProcess = nil
wavesProc.currentWaveIndex = wavesProc.currentWaveIndex + 1
if wavesProc.currentWaveIndex > #wavesProc.wavesTemplate.waves then
CompletedWavesProcess(wavesProc)
wavesProc.isClear = true
else
wavesProc.currentWaveTemplate = wavesProc.wavesTemplate.waves[wavesProc.currentWaveIndex]
end
end
end
return true
end
function UpdateOrdersAllWavesProcess()
local t = AllWavesProcesses()
for i, wavesProc in ipairs(t) do
local waveProc = wavesProc.currentWaveProcess
if waveProc ~= nil then
for i2, mobCell in ipairs(waveProc.mobCells) do
for i3, unit in ipairs(mobCell.units) do
SetUnitX(unit, GetUnitX(unit))
SetUnitY(unit, GetUnitY(unit))
IssuePointOrderLoc(unit, "attack", waveProc.targetLocation)
end
end
end
end
return true
end
function UpdateAllWavesProcess()
local t = AllWavesProcesses()
for i, wavesProc in ipairs(t) do
UpdateWavesProcess(wavesProc)
end
return true
end
function WavesUnitDead(unit)
local t = AllWavesProcesses()
for i, wavesProc in ipairs(t) do
local wProc = wavesProc.currentWaveProcess
if wProc ~= nil then
if WaveProcessDeadUnit( wProc, unit) then return true end
end
end
return false
end
function WavesUnitSummon(summoning, summoned)
local t = AllWavesProcesses()
for i, wavesProc in ipairs(t) do
local wProc = wavesProc.currentWaveProcess
if wProc ~= nil then
if WaveProcessSummonUnit( wProc, summoning, summoned) then return true end
end
end
return false
end
function WavesUnitChangeOwner(unit, prevPlayer, newPlayer)
local t = AllWavesProcesses()
for i, wavesProc in ipairs(t) do
local wProc = wavesProc.currentWaveProcess
if wProc ~= nil then
if WaveProcessUnitChangePlayer( wProc, unit, prevPlayer, newPlayer) then return true end
end
end
return false
end
testProcess = nil
function Story1CreateWavesEndAct()
local template = CreateWavesTemplate()
template.funcsOnCompleted = {
function(wavesProcess)
--print("completed!")
udg_exit_lock = false
local unit = wavesProcess.forge
local txt = "Congratulations! You won.\nYou can continue your current journey, or enter '/kill' to return to the sanctuary, improve souls and select the next task."
local name = "Forge"
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
ConditionalTriggerExecute(gg_trg_Act1Win)
RemoveUnit(unit)
end
}
local unGet = function(id) return AllDataUnits():GetUnit(FourCC(id)) end
local unRand = function(level) return AllDataUnits():GetUnitForLevel( level ) end
--WaveTemplateGetWave(template, 1).startFunctions = {function() print("wave 1 start") end}
--WaveTemplateGetWave(template, 2).startFunctions = {function() print("wave 2 start") end}
--WaveTemplateGetWave(template, 3).startFunctions = {function() print("wave 3 start") end}
--WaveTemplateGetWave(template, 4).startFunctions = {function() print("wave 4 start") end}
--WaveTemplateGetWave(template, 5).startFunctions = {function() print("wave 5 start") end}
--WaveTemplateGetWave(template, 6).startFunctions = {function() print("wave 6 start") end}
local flow = nil
-- wave #1
WaveTemplateGetWave(template, 1).multPower = 1
flow = WaveTemplateAddFlowUniq(template, 1, FourCC("Ulic"), -1, "Techno Lich")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 20, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AUdr"))
SelectHeroSkill(unit, FourCC("AUfn"))
SelectHeroSkill(unit, FourCC("AUfu"))
end
SelectHeroSkill(unit, FourCC("AUdd"))
local txt = "Security protocol initialized. The bodies of previous aggressors will be used as undead shield."
local name = GetHeroProperName(unit)
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
flow = WaveTemplateAddFlowUniq(template, 1, FourCC("Udea"), 1, "Techno Dead Knight")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rhe3") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 20, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AUdc"))
SelectHeroSkill(unit, FourCC("AUdp"))
SelectHeroSkill(unit, FourCC("AUau"))
end
SelectHeroSkill(unit, FourCC("AUan"))
local txt = "The threat has been identified. I'm launching a protocol for her elimination."
local name = GetHeroProperName(unit)
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
WaveTemplateAddFlow(template, 1, unGet("nskg"), 100, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 1, unGet("nskf"), 100, 1, 0.5, nil, nil)
flow = WaveTemplateAddFlow(template, 1, unGet("nrvd"), 100, 1, 0.25, nil, nil)
flow.funcsOnDeath = {
function(unit)
if math.random(1, 2) == 1 then
CreateItem( FourCC("rsps") , GetUnitX(unit), GetUnitY(unit))
end
end
}
-- wave #2
WaveTemplateGetWave(template, 2).multPower = 1
flow = WaveTemplateAddFlowUniq(template, 2, FourCC("Hamg"), -1, "Enchanter of the Forge")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 20, false)
for i = 1, 4 do
SelectHeroSkill(unit, FourCC("AHbz"))
SelectHeroSkill(unit, FourCC("AHab"))
SelectHeroSkill(unit, FourCC("AHwe"))
SelectHeroSkill(unit, FourCC("ANms"))
end
local txt = "You are making a mistake. This forge is our last hope for deliverance from the yoke of the so-called gods."
local name = GetHeroProperName(unit)
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
flow = WaveTemplateAddFlowUniq(template, 2, FourCC("Hpal"), 0, "Defender of the Forge's Defenders")
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 20, false)
for i = 1, 4 do
SelectHeroSkill(unit, FourCC("AHhb"))
SelectHeroSkill(unit, FourCC("AHds"))
SelectHeroSkill(unit, FourCC("AHad"))
SelectHeroSkill(unit, FourCC("AHbh"))
end
end
}
flow = WaveTemplateAddFlowUniq(template, 2, FourCC("Hmkg"), 2, "Desperate Forge Defender")
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 20, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AHtc"))
SelectHeroSkill(unit, FourCC("AHtb"))
SelectHeroSkill(unit, FourCC("AHbh"))
end
SelectHeroSkill(unit, FourCC("AHav"))
end
}
WaveTemplateAddFlow(template, 2, unGet("nrog"), 100, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 2, unGet("nass"), 100, 1, 0.35, nil, nil)
WaveTemplateAddFlow(template, 2, unGet("nhdc"), 40, 1, 0.55, nil, nil)
WaveTemplateAddFlow(template, 2, unGet("nbld"), 50, 1, 0.3, nil, nil)
WaveTemplateAddFlow(template, 2, unGet("nwzd"), 100, 1, 0.7, nil, nil)
-- wave #3
WaveTemplateGetWave(template, 3).multPower = 1
flow = WaveTemplateAddFlowUniq(template, 3, FourCC("nwiz"), -1, "Mechanic")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
local txt = "The golem activation process is complete. I'm putting them in defensive mode."
local name = "Mechanic"
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
WaveTemplateAddFlow(template, 3, unGet("nwiz"), 60, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 3, unGet("nban"), 100, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 3, unGet("ngrk"), 100, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 3, unGet("narg"), 100, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 3, unGet("ngst"), 30, 1, 1, nil, nil)
-- wave #4
WaveTemplateGetWave(template, 4).multPower = 1.15
flow = WaveTemplateAddFlowUniq(template, 4, FourCC("noga"), -1, "Wow, what a big one")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
local txt = "We will lay down our lives for freedom. And who are you fighting for, heroes without souls? For those who deprived you of your will?"
local name = "Ogre's father"
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
WaveTemplateAddFlow(template, 4, unGet("nfsh"), 40, 1.2, 0, nil, nil)
WaveTemplateAddFlow(template, 4, unGet("nftb"), 100, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 4, unGet("nftk"), 50, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 4, unGet("nomg"), 30, 0, 0, nil, nil)
WaveTemplateAddFlow(template, 4, unGet("nogm"), 100, 0, 1, nil, nil)
WaveTemplateAddFlow(template, 4, unGet("nogl"), 40, 0.5, 1, nil, nil)
-- wave #5
WaveTemplateGetWave(template, 5).multPower = 1.3
flow = WaveTemplateAddFlowUniq(template, 5, FourCC("Hblm"), -1, "Homunculologist")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 25, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AHfs"))
SelectHeroSkill(unit, FourCC("AHbn"))
SelectHeroSkill(unit, FourCC("AHdr"))
end
SelectHeroSkill(unit, FourCC("AHpx"))
local txt = "It pains me to see how once noble heroes embarked on the path of genocide. I can't let you destroy this forge. Let me introduce you to her creations."
local name = GetHeroProperName(unit)
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
WaveTemplateAddFlow(template, 5, unGet("nina"), 50, 1, 0, nil, nil)
WaveTemplateAddFlow(template, 5, unGet("nbal"), 100, 0, 0, nil, nil)
WaveTemplateAddFlow(template, 5, unGet("nfgb"), 100, 0, 0, nil, nil)
WaveTemplateAddFlow(template, 5, unGet("ninc"), 100, 0, 0, nil, nil)
-- wave #6
WaveTemplateGetWave(template, 6).multPower = 1.4
flow = WaveTemplateAddFlowUniq(template, 6, FourCC("Ekee"), -1, "Druid of the Forge")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 30, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AEer"))
SelectHeroSkill(unit, FourCC("AEfn"))
SelectHeroSkill(unit, FourCC("AEah"))
end
SelectHeroSkill(unit, FourCC("AEtg"))
local txt = "Fight to the death, brothers and sister. We are the last hope of this world."
local name = GetHeroProperName(unit)
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, name, nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
}
flow = WaveTemplateAddFlowUniq(template, 6, FourCC("Emoo"), 0.33, "Forge Priestess")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 30, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AOcr"))
SelectHeroSkill(unit, FourCC("AHfa"))
SelectHeroSkill(unit, FourCC("AEar"))
end
SelectHeroSkill(unit, FourCC("AEsf"))
end
}
flow = WaveTemplateAddFlowUniq(template, 6, FourCC("Edem"), 0.66, "Forge test subject")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 30, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AEmb"))
SelectHeroSkill(unit, FourCC("AEim"))
SelectHeroSkill(unit, FourCC("AEev"))
end
SelectHeroSkill(unit, FourCC("AEme"))
end
}
flow = WaveTemplateAddFlowUniq(template, 6, FourCC("Ewar"), 1, "Forge Guardian")
flow.funcsOnDeath = {function(unit) CreateItem( FourCC("rma2") , GetUnitX(unit), GetUnitY(unit)) end}
flow.funcsOnSpawn = {
function(unit)
SetHeroLevel(unit, 30, false)
for i = 1, 3 do
SelectHeroSkill(unit, FourCC("AEev"))
SelectHeroSkill(unit, FourCC("AEfk"))
SelectHeroSkill(unit, FourCC("AEsh"))
end
SelectHeroSkill(unit, FourCC("AEsv"))
end
}
WaveTemplateAddFlow(template, 6, unGet("nwlg"), 100, -0.25, 0, nil, nil)
WaveTemplateAddFlow(template, 6, unGet("ngdk"), 50, 0, 0, nil, nil)
WaveTemplateAddFlow(template, 6, unGet("nsth"), 50, 0, 0, nil, nil)
WaveTemplateUpdateInfo(template)
return template
end
function CreateTestWaves()
local template = CreateWavesTemplate()
local uns = {}
uns[1] = AllDataUnits():GetUnitForLevel( 1 )
uns[2] = AllDataUnits():GetUnitForLevel( 2 )
uns[3] = AllDataUnits():GetUnitForLevel( 2 )
uns[4] = AllDataUnits():GetUnitForLevel( 3 )
uns[5] = AllDataUnits():GetUnitForLevel( 3 )
uns[6] = AllDataUnits():GetUnitForLevel( 3 )
uns[7] = AllDataUnits():GetUnitForLevel( 4 )
uns[8] = AllDataUnits():GetUnit(FourCC("nhyd")) -- AllDataUnits():GetUnitForLevel( 3 )
local fReplica2 = function(unit)
local txt = "Hi!"
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, "mob", nil, txt, bj_TIMETYPE_SET, 6.00, false)
end
WaveTemplateAddFlow(template, 1, uns[1], 0.3, 4, 0, nil, {fReplica2})
WaveTemplateAddFlow(template, 1, uns[2], 0.9, 4)
WaveTemplateAddFlow(template, 2, uns[3], 10, 4)
WaveTemplateAddFlow(template, 2, uns[4], 20, 4)
WaveTemplateAddFlowUniq(template, 2, FourCC("hrif"), -1, "Eric")
WaveTemplateAddFlow(template, 3, uns[6], 20, 4)
WaveTemplateAddFlow(template, 3, uns[7], 30, 4)
WaveTemplateAddFlowUniq(template, 3, FourCC("hrif"), -1, "Dvorn")
template.waves[2].startFunctions = {function() print("start wave 2") end}
template.waves[3].startFunctions = {function() print("start wave 3") end}
template.waves[1].startFunctions = {function() print("start wave 1") end}
local fReplica = function(unit)
local txt = "It pains me to see how once noble heroes have taken the path of genocide. I cannot allow you to destroy this forge. Let me introduce you to her creations."
TransmissionFromUnitWithNameBJ(GetPlayersAll(), unit, GetHeroProperName(unit), nil, txt, bj_TIMETYPE_SET, 20.00, false)
end
WaveTemplateAddFlowUniq(template, 3, FourCC("Hamg"), 1, "Arkhor", {fReplica, fReplica})
WaveTemplateAddFlow(template, 3, uns[8], 40, 4)
WaveTemplateUpdateInfo(template)
return template
end
function TestCreateWavesProcess(wavesTemplate, power1, power2)
local outPositions = {{x=-11933, y=11701},
{x=-8911, y=11698},
{x=-11939, y=8714},
{x=-8942, y=8721}}
local target = {x=-10436, y=10232}
local process = CreateWavesProcess(wavesTemplate, udg_xPlayer, power1, power2, outPositions, target)
return process
end
function TestCreateWaveProcess(wavesTemplate)
local waveTemplate = wavesTemplate.waves[3]
local outPositions = {{x=-10650, y=11079}, {x=-10062, y=11057}}
local target = {x=-9305, y=8797}
local process = CreateWaveProcess(waveTemplate, udg_xPlayer, 2000, 15000, outPositions, target)
return process
end
function WavesDebugPrint(template)
print("Waves count " .. #template.waves)
for waveNumber = 1, #template.waves do
local wave = WaveTemplateGetWave(template, waveNumber)
print(" flows count " .. #wave.flows)
for flowNumber = 1, #wave.flows do
local flow = wave.flows[flowNumber]
print(" (countType, weight, percent ", flow.countType, flow.powerWeight, flow.powerPercent )
end
end
end
function DebugPrintWaves()
local t = AllWavesProcesses()
for i, wavesProc in ipairs(t) do
print("Process Wave #" .. i)
local wProc = wavesProc.currentWaveProcess
local wTemp = wProc.waveTemplate
print(" mobCells Count " .. #wProc.mobCells)
print(" queueFlowsSpawn Count " .. #wProc.queueFlowsSpawn)
for i2, fProc in ipairs(wProc.flowProcesses) do
print(" flow #" .. i2)
print(" inBattle " .. fProc.inBattle)
print(" inQueue " .. fProc.inQueue)
print(" reserv " .. fProc.reserv)
print(" initalCount " .. fProc.initalCount)
print(" maxInBattle " .. fProc.maxInBattle)
end
end
end
_B22Templates = nil
function B22Templates()
if _B22Templates == nil then _B22Templates = {} end
return _B22Templates
end
function B22Generated()
_B22Templates = {}
local templates = B22Templates()
function f(power, count, difs)
local obs = {}
if selectedModes.siegeCraft.isOn then
power = power * 1.2
end
for i = 1, #difs do
local multPower = difs[i]*(1+math.random()*0.07)
local newPower = Dif().power * power * multPower
local flows = math.random(count.min, count.max)
table.insert(obs, {power=newPower, flows=flows, multPower=multPower})
end
return {power=Dif().power * power, obs=obs}
end
local data =
{
f( 850, {min=1, max=2}, {1.0} ),
f( 1190, {min=2, max=3}, {1.0} ),
f( 1666, {min=2, max=3}, {1.0, 1.1} ),
f( 2330, {min=2, max=3}, {1.0, 1.15} ),
f( 3265, {min=2, max=4}, {1.0, 1.15, 1.2} ),
f( 4570, {min=3, max=4}, {1.0, 1.18, 1.25} )
}
local abilities = {FourCC("A01J"), FourCC("A01I"), FourCC("A01K"), FourCC("A01G"), FourCC("A01H"), FourCC("A01F") }
for i = 1, 6 do
local wavesTemplate = B22GenerateWaveTemplate( data[i].obs )
table.insert(templates, {wavesTemplate=wavesTemplate, power1=data[i].power, power2=4*data[i].power})
B22UpdateTextAbil(abilities[i], data[i].power, 4*data[i].power, wavesTemplate)
end
return templates
end
function B22Abil2Process( ability )
local i = B22Ability2Index( ability )
local template = B22Templates()[i].wavesTemplate
local power1 = B22Templates()[i].power1
local power2 = B22Templates()[i].power2
return TestCreateWavesProcess(template, power1, power2)
end
function B22Ability2Index( ability )
local abilities = {FourCC("A01J"), FourCC("A01I"), FourCC("A01K"), FourCC("A01G"), FourCC("A01H"), FourCC("A01F") }
for i = 1, 6 do
if ability == abilities[i] then return i end
end
return 0
end
function B22UpdateTextAbil(abil, power, power2, waves)
--BlzSetAbilityTooltip(abil, "Assignment: |cffff6464" .. "ABIL" .. "|r", 0)
--local rewards = B22CreateRewards( 6 )
local rewards = _b22RewardsSets[B22Ability2Index( abil )]
local rewardsText = "Rewards\n" .. B22RewardsToString( rewards )
BlzSetAbilityExtendedTooltip(abil, WavesToString( power, power2, waves ) .. "\n|cffaaaaaa------------------|r\n" .. rewardsText, 0)
end
function TestRewards()
return "Rewards\n" .. "|cffff6464•|r 15 gold\n" .. "• 15 gold\n"
end
function WavesToString( power, power2, waves )
local str = "|cffaaaaaa(in |cffffcc00x|r/|cffff8a00y|r|cffaaaaaa format, |cffffcc00x|r|cffaaaaaa - quantity on the battlefield, |cffff8a00y|r|cffaaaaaa - quantity in reserve)|r\nEnemies"
for waveNumber = 1, #waves.waves do
local wave = WaveTemplateGetWave(waves, waveNumber)
str = str .. "\n" .. WaveToString( power, power2, wave, waveNumber )
end
return str
end
function WaveToString( power, power2, wave, n )
local str = " Wave " .. n
for flow = 1, #wave.flows do
str = str .. "\n" .. FlowToString( power, power2, wave, wave.flows[flow] )
end
return str
end
function FlowToString( power, power2, wave, flow )
--local tcount = "#|cffffcc00" .. 40 .. "|r⮕#|cffff8a00" .. 160 .. "|r"
local count, count2 = 1, 4
if flow.monsterDB ~= nil then
local tempPower = wave.multPower
local unitPower = flow.monsterDB:GetPower()
local squadPower = power * tempPower * flow.powerPercent
count = math.floor(0.5 + squadPower / unitPower)
if count < 1 then count = 1 end
local squadPower2 = power2 * tempPower * flow.powerPercent
count2 = math.floor(0.5 + squadPower2 / unitPower)
if count2 < 1 then count2 = 1 end
end
local tcount = "number |cffffcc00" .. count .. "|r/" .. "|cffff8a00" .. count2 .. "|r"
return " |cffff8f8f" .. flow.monsterDB.name .. " (L" .. flow.monsterDB.level .. ")|r, " .. tcount
end
_b22RTxts = nil
_b22RewardsSets = nil
function B22GeneratedRewards()
_b22RewardsSets = {}
for i = 1, 6 do
_b22RewardsSets[i] = B22CreateRewards( i )
end
end
function B22CreateRewards( count )
function f(value, extraMult)
if selectedModes.siegeCraft.isOn then
return value * 1.5 * extraMult
end
return value * extraMult
end
local extraMult = 1.0 + (count - 1) * 0.2
local rewards = {
{id="hpMerc", value=0, mult=f(25.0, extraMult)},
{id="mnMerc", value=0, mult=f(25.0, extraMult)},
{id="hpSummon", value=0, mult=f(18.0, extraMult)},
{id="dmgMerc", value=0, mult=f(1.5, extraMult)},
{id="dmgSummon", value=0, mult=f(1.5, extraMult)},
{id="timeSummon", value=0, mult=f(1.5, extraMult)},
}
for i = 1, count do
local r = math.random(1, 6)
rewards[r].value = rewards[r].value + rewards[r].mult
end
return rewards
end
function B22RewardsToString( rewards )
local txt = ""
if _b22RTxts == nil then
_b22RTxts = {
hpMerc="maximum health of allied mercenaries",
mnMerc="maximum mana of allied mercenaries",
hpSummon="maximum health of allied summons",
dmgMerc="base damage of all mercenaries",
dmgSummon="base damage of all summons",
timeSummon="duration of summoned existence"
}
end
for i = 1, #rewards do
if rewards[i].value > 0 then
txt = txt .. "|cff4C9B3F•|r |cffFFE600+" .. rewards[i].value .. "|r " .. _b22RTxts[rewards[i].id] .. "\n"
end
end
return txt
end
function B22GenerateWaveTemplate( tempInfo ) -- power 850
local template = CreateWavesTemplate()
local unRand = function(level) return AllDataUnits():GetUnitForLevel( level ) end
local unRand2 = function(power, minCount, maxCount) return unRand(RandLevelForPower(power, minCount, maxCount)) end
template.funcsOnCompleted = {
function(wavesProcess)
RunRemoveBlocker("def")
local unit = wavesProcess.forge
RemoveUnit(unit)
local map = {
hpMerc="mercExtraHP",
mnMerc="mercExtraMP",
hpSummon="summonExtraHP",
dmgMerc="mercExtraDamage",
dmgSummon="summonExtraDamage",
timeSummon="summonExtraDuration"
}
--if ForGroupBJ(udg_mercs_all_mercs_group, Trig_Purgatory_Start_Func002Func005A)
local rewards = wavesProcess.rewards
local runInfo = CurrentRun()
for i = 1, #rewards do
if rewards[i].value > 0 then
runInfo[map[rewards[i].id]] = runInfo[map[rewards[i].id]] + rewards[i].value
--txt = txt .. "|cff4C9B3F•|r |cffFFE600+" .. rewards[i].value .. "|r " .. _b22RTxts[rewards[i].id] .. "\n"
globalValue = 0
function fAddHP()
local un = GetEnumUnit()
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = udg_run_stats_mercMultHP[playerIndex]
v = v * globalValue
v = v + GetUnitStateSwap(UNIT_STATE_MAX_LIFE, un)
BlzSetUnitMaxHP(un, R2I(v))
end
function fAddMP()
local un = GetEnumUnit()
if GetUnitStateSwap(UNIT_STATE_MAX_MANA, un) <= 0 then return end
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = globalValue + GetUnitStateSwap(UNIT_STATE_MAX_MANA, un)
BlzSetUnitMaxMana(un, R2I(v))
end
function fAddDamage()
local un = GetEnumUnit()
local playerIndex = GetConvertedPlayerId(GetOwningPlayer(un))
local v = udg_run_stats_mercMultDamage[playerIndex]
v = v * globalValue
BlzSetUnitBaseDamage(un, R2I(v + I2R(BlzGetUnitBaseDamage(un, 0))), 0)
BlzSetUnitBaseDamage(un, R2I(v + I2R(BlzGetUnitBaseDamage(un, 1))), 1)
end
if rewards[i].id == "hpMerc" then
globalValue = rewards[i].value
ForGroupBJ(udg_mercs_all_mercs_group, fAddHP)
end
if rewards[i].id == "mnMerc" then
globalValue = rewards[i].value
ForGroupBJ(udg_mercs_all_mercs_group, fAddMP)
end
if rewards[i].id == "dmgMerc" then
globalValue = rewards[i].value
ForGroupBJ(udg_mercs_all_mercs_group, fAddDamage)
end
end
end
--print( B22RewardsToString( wavesProcess.rewards ) )
ConditionalTriggerExecute(gg_trg_B22Finish)
end
}
local minCounts = {5, 2, 3, 1, 2, 2, 4, 4, 4, 4}
for wave = 1, #tempInfo do
WaveTemplateGetWave(template, wave).multPower = tempInfo[wave].multPower
local powers = Normalization(tempInfo[wave].power, tempInfo[wave].flows, 0.35)
for flow = 1, tempInfo[wave].flows do
WaveTemplateAddFlow(template, wave, unRand2(powers[flow], minCounts[flow], 15), powers[flow], 1, 0, nil, nil)
end
end
WaveTemplateUpdateInfo(template)
--ddd(4600, 4600*4, template)
return template
end
function FillTexture( sx, sy, fx, fy, brush )
for x=sx, fx, 128.0 do
for y=sy, fy, 128.0 do
SetTerrainType(x, y, brush, -1, 1, 1)
end
end
end
function FillTexture2( x, y, widht, height, brush)
FillTexture(x-widht/2, y-height/2, x+widht/2, y+height/2, brush)
end
function FillTextureRegion( region, brush )
local sx = GetRectMinX( region )
local sy = GetRectMinY( region )
local fx = GetRectMaxX( region )
local fy = GetRectMaxY( region )
for x=sx, fx, 128.0 do
for y=sy, fy, 128.0 do
SetTerrainType(x, y, brush, -1, 1, 1)
end
end
end
function GeneratePath( currentLayer, act, circleUnit, buildingX, buildingY, out )
local nextLayer = RunNextDepth(currentLayer)
--if nextLayer > udg_chunk_layers then nextLayer = udg_chunk_layers end
--if nextLayer < 1 then nextLayer = 1 end
local extraDanger = nextLayer - currentLayer
SetUnitColor(circleUnit, udg_chunk_colors_layer_unit[extraDanger])
if locationSets == nil then
locationSets = GenerateLocationsSet(layer, act)
end
local location = randLocation( nextLayer )
local player = Player(PLAYER_NEUTRAL_PASSIVE)
local buildingType = udg_location_specter_building[location]
local un = CreateUnit( player, buildingType, buildingX, buildingY, bj_UNIT_FACING )
GroupAddUnit( udg_exit_specter_unitsBuildings, un)
PauseUnit(un, true)
SetUnitInvulnerable(un, true)
local scale = (BlzGetUnitRealField(un, UNIT_RF_SCALING_VALUE) * 50.00)
SetUnitScalePercent(un, scale, scale, scale)
SetUnitVertexColorBJ(un, 100, 100, 100, 25.00)
--FillTexture( buildingX-32, buildingY-32, buildingX+32, buildingY+32, udg_tile_type[nextLayer])
FillTexture2( buildingX, buildingY, 128, 128, udg_tile_type[nextLayer])
local w = udg_chunk_exit_textureWidthDepth2[ out ]
local h = udg_chunk_exit_textureHeightDepth2[ out ]
FillTexture2( GetUnitX(circleUnit), GetUnitY(circleUnit), w, h, udg_tile_type[nextLayer])
udg_exitDepth2_location[out] = location
udg_exitDepth2_layer[out] = nextLayer
end
_oldMonsters = nil
function OldMonsters()
if _oldMonsters == nil then
_oldMonsters = {}
_oldMonsters[1] = {{level={1, 2}, count={2,3}}}
_oldMonsters[2] = {{level={1, 2}, count={2,3}}, {level={3, 5}, count={2,3}}}
_oldMonsters[3] = {{level={1, 2}, count={2,3}}, {level={3, 5}, count={2,3}}}
_oldMonsters[4] = {{level={1, 3}, count={2,4}}, {level={3, 6}, count={1,2}}, {level={6, 10}, count={0,1}}}
_oldMonsters[5] = {{level={2, 4}, count={2,5}}, {level={3, 6}, count={2,4}}, {level={6, 10}, count={1,1}}}
_oldMonsters[6] = {{level={2, 4}, count={3,7}}, {level={3, 6}, count={3,5}}, {level={7, 10}, count={1,1}}}
_oldMonsters[7] = {{level={3, 5}, count={3,6}}, {level={4, 7}, count={3,5}}, {level={7, 10}, count={1,2}}}
_oldMonsters[8] = {{level={3, 5}, count={4,6}}, {level={4, 7}, count={3,5}}, {level={7, 10}, count={1,3}}}
_oldMonsters[9] = {{level={4, 6}, count={5,7}}, {level={5, 7}, count={4,7}}, {level={7, 10}, count={1,3}}}
_oldMonsters[10] = {{level={4, 6}, count={5,7}}, {level={5, 7}, count={4,7}}, {level={8, 10}, count={1,3}}}
_oldMonsters[11] = {{level={4, 6}, count={5,8}}, {level={5, 7}, count={4,7}}, {level={8, 10}, count={1,4}}}
_oldMonsters[12] = {{level={4, 6}, count={5,8}}, {level={5, 7}, count={4,8}}, {level={8, 10}, count={2,4}}}
_oldMonsters[13] = {{level={4, 6}, count={5,9}}, {level={5, 7}, count={4,8}}, {level={8, 10}, count={3,5}}}
_oldMonsters[14] = {{level={4, 6}, count={5,10}}, {level={5, 7}, count={4,9}}, {level={8, 10}, count={3,6}}}
_oldMonsters[15] = {{level={4, 6}, count={5,12}}, {level={5, 7}, count={5,12}}, {level={8, 10}, count={3,7}}}
_oldMonsters[16] = {{level={7, 10}, count={7,12}}, {level={7, 10}, count={7,12}}, {level={8, 10}, count={3,7}}}
end
return _oldMonsters
end
OldMonstersInExits = { {}, {}, {}, {} }
function PreloadMonstersMethodA(layer, exitNumber)
local monsters = {}
local template = OldMonsters()[layer]
if layer < 1 then return {} end
for i = 1, #template do
local level = math.random(template[i].level[1], template[i].level[2])
local count = math.random(template[i].count[1], template[i].count[2])
if count > 0 then
local ob = {level=level, count=count}
ob.unitDB = AllDataUnits():GetUnitForLevel( level )
table.insert(monsters, ob)
end
end
OldMonstersInExits[exitNumber] = monsters
return monsters
end
function CreatePreloadUnits(monsters)
for slot = 1, #monsters do
end
end
function CreateWaveCooldownTimerTriggers()
for i = 1, 10, 1 do
local wave_cooldown_trigger = CreateTrigger()
udg_wave_slot_cooldownTimer[i] = CreateTimer()
TriggerRegisterTimerExpireEvent(wave_cooldown_trigger, udg_wave_slot_cooldownTimer[i])
TriggerAddAction(wave_cooldown_trigger, function()
udg_wave_slot_isCooldown[i] = false
end)
end
end