- Joined
- Nov 30, 2007
- Messages
- 1,202
It should be pretty straight forward to switch languages now that all (most) of the UI components are changeable. I am myself too lazy to do all of it, but I did create a basic manager for all strings using a table array, as well as translation binding to unit types.
This library only allow you to configure different language definitions which you can use to update the UI using wrapper methods for SetUnitName, SetItemName, SetAbilityTooltip, and so on. It does however not automatically update strings when the language is changed (except for units entering map). Not sure if I should keep this feature, expand on it or remove it entierly.
Example:
Not sure exactly how to handle the HeroProperNames, though one can use the wrapper method to override the Object editor proper name.
If you happen to have an idea of why the extended research tooltip is being clipped do tell.
Finally, do post the natives that let you change labels of unit/items being sold/trained, can't seem to find them.
This library only allow you to configure different language definitions which you can use to update the UI using wrapper methods for SetUnitName, SetItemName, SetAbilityTooltip, and so on. It does however not automatically update strings when the language is changed (except for units entering map). Not sure if I should keep this feature, expand on it or remove it entierly.
JASS:
// WORK IN PROGRESS...
library Translator uses Table, PArrayList optional WorldBounds
globals
constant integer NO_LANGUAGE = 0
constant integer EN = 1
constant integer SE = 2
constant integer DE = 3
private constant integer DEFAULT_LANGUAGE = 2
private constant integer NUM_SUPPORTED_LANGUAGES = 3
private constant boolean REPLACE_ENTERING_UNITS_WITH_BASE_UNIT = true
endglobals
// ~ ITEM-TYPE TRANSLATOR LIBRARY uses STRINGS TRANSLATOR
struct ItemTranslator
private static Table table
private integer itemTypeId
integer nameId
integer descriptionId
integer tooltipId
integer extendedTooltipId
static method create takes integer itemTypeId returns thistype
local thistype this
if thistype.table.has(itemTypeId) then
return 0
endif
set this = .allocate()
set this.itemTypeId = itemTypeId
set thistype.table[itemTypeId] = this
return this
endmethod
method destroy takes nothing returns nothing
call thistype.table.remove(.itemTypeId)
set .nameId = 0
set .descriptionId = 0
set .tooltipId = 0
set .extendedTooltipId = 0
call .deallocate()
endmethod
static method get takes integer itemTypeId returns thistype
return thistype.table[itemTypeId]
endmethod
static method has takes integer itemTypeId returns boolean
return thistype.table.has(itemTypeId)
endmethod
private static method onInit takes nothing returns nothing
set thistype.table = Table.create()
endmethod
endstruct
// ~ ABILITY TRANSLATOR LIBRARY uses STRINGS TRANSLATOR
private struct AbilityLevel
integer tooltip
integer activatedTooltip
integer activatedExtendedTooltip
integer extendedTooltip
integer researchTooltip
integer researchExtendedTooltip
method onDestroy takes nothing returns nothing
set .tooltip = 0
set .activatedTooltip = 0
set .activatedExtendedTooltip = 0
set .extendedTooltip = 0
set .researchTooltip = 0
set .researchExtendedTooltip = 0
endmethod
//! runtextmacro SPELL_WRAPPER("Tooltip", " BlzSetAbilityTooltip", ".tooltip")
//! runtextmacro SPELL_WRAPPER("ActivatedTooltip", " BlzSetAbilityActivatedTooltip", ".activatedTooltip")
//! runtextmacro SPELL_WRAPPER("ActivatedExtendedTooltip", " BlzSetAbilityActivatedExtendedTooltip", ".activatedExtendedTooltip")
//! runtextmacro SPELL_WRAPPER("ExtendedTooltip", " BlzSetAbilityExtendedTooltip", ".extendedTooltip")
//! runtextmacro SPELL_WRAPPER("ResearcTooltip", " BlzSetAbilityResearchTooltip", ".researchTooltip")
//! runtextmacro SPELL_WRAPPER("ResearchExtendedTooltip", " BlzSetAbilityResearchExtendedTooltip", ".researchExtendedTooltip")
//! textmacro_once SPELL_WRAPPER takes NAME, NATIVE, VAR
method set$NAME$ takes player p, integer abilCode, integer level returns nothing
local string s = Strings.load(p, $VAR$)
if s != null then
call $NATIVE$(abilCode, s, level)
endif
endmethod
//! endtextmacro
endstruct
struct AbilityTranslator
private integer maxLevel
private integer abilCode
private Table abil
static method create takes integer abilCode, integer maxLevel returns thistype
local thistype this = .allocate()
local integer i = 0
set this.maxLevel = maxLevel
set this.abilCode = abilCode
set this.abil = Table.create()
loop
exitwhen i == maxLevel
set this.abil[i] = AbilityLevel.create()
set i = i + 1
endloop
return this
endmethod
method destroy takes nothing returns nothing
local AbilityLevel ab
local integer i = 0
loop
exitwhen i == .maxLevel
set ab = .abil[i]
call ab.destroy()
set i = i + 1
endloop
call .abil.destroy()
call .deallocate()
endmethod
//! runtextmacro SPELL_TRANS_SETTERS("Tooltip", "tooltip")
//! runtextmacro SPELL_TRANS_SETTERS("ActivatedTooltip", "activatedTooltip")
//! runtextmacro SPELL_TRANS_SETTERS("ActivatedExtendedTooltip", "activatedExtendedTooltip")
//! runtextmacro SPELL_TRANS_SETTERS("ExtendedTooltip", "extendedTooltip")
//! runtextmacro SPELL_TRANS_SETTERS("ResearchTooltip", "researchTooltip")
//! runtextmacro SPELL_TRANS_SETTERS("ResearchExtendedTooltip", "researchExtendedTooltip")
//! textmacro_once SPELL_TRANS_SETTERS takes NAME, VAR
method set$NAME$ takes integer level, integer stringId returns nothing
local AbilityLevel ab
if level <= 0 or level >= .maxLevel then
// Level out of bounds
return
endif
set ab = .abil[level - 1]
set ab.$VAR$ = stringId
endmethod
//! endtextmacro
method updatePlayerUI takes player p returns nothing
local AbilityLevel ab
local integer lvl = 1
if p == GetLocalPlayer() then
loop
exitwhen lvl == .maxLevel + 1
set ab = .abil[lvl - 1]
call ab.setTooltip(p, .abilCode, lvl)
call ab.setActivatedTooltip(p, .abilCode, lvl)
call ab.setActivatedExtendedTooltip(p, .abilCode, lvl)
call ab.setExtendedTooltip(p, .abilCode, lvl)
call ab.setResearcTooltip(p, .abilCode, lvl)
call ab.setResearchExtendedTooltip(p, .abilCode, lvl)
set lvl = lvl + 1
endloop
endif
endmethod
method updateUI takes nothing returns nothing
local integer i = 0
loop
exitwhen i == bj_MAX_PLAYER_SLOTS // needs fix
call .updatePlayerUI(Player(i)) // needs fix
set i = i + 1
endloop
endmethod
endstruct
// ~ UNIT-TYPE LANGUAGE LIMIT LIBRARY uses STRINGS TRANSLATOR
struct UnitLimitTranslator
static constant integer UNLIMITED = -1
private static IntArrayList instances
private integer languageId
private integer unitTypeId
integer normalLimit // Must be made player local instead of global
static method create takes integer languageId, integer unitTypeId, integer normalLimit returns thistype
local thistype this = .allocate()
call thistype.instances.add(thistype.instances.size(), this)
set this.languageId = languageId
set this.unitTypeId = unitTypeId
set this.normalLimit = normalLimit
return this
endmethod
method destroy takes nothing returns nothing
local integer index = thistype.instances.indexOf(this, true)
call thistype.instances.remove(index)
call .deallocate()
endmethod
static method updatePlayerUI takes player p returns nothing
local thistype this
local integer i = 0
loop
exitwhen i == thistype.instances.size()
set this = thistype.instances[i]
if Strings.getPlayerLaguage(p) == this.languageId then
call SetPlayerTechMaxAllowedSwap(this.unitTypeId, this.normalLimit, p)
else
call SetPlayerTechMaxAllowedSwap(this.unitTypeId, 0, p)
endif
set i = i + 1
endloop
endmethod
static method updateUI takes nothing returns nothing
local integer i = 0
loop
exitwhen i == bj_MAX_PLAYER_SLOTS // needs fix
call .updatePlayerUI(Player(i)) // needs fix
set i = i + 1
endloop
endmethod
private static method onInit takes nothing returns nothing
set thistype.instances = IntArrayList.create()
endmethod
endstruct
// Need hooks for changing player unit type limits...
private struct UnitTranslator
static Table table
private Table attached
integer unitNameId
integer baseUnitTypeId
static method create takes integer unitTypeId returns thistype
local thistype this
if thistype.table.has(unitTypeId) then
return 0
endif
set this = .allocate()
set this.baseUnitTypeId = unitTypeId
set thistype.table[unitTypeId] = this
return this
endmethod
method onLanguageChange takes player p returns nothing
endmethod
static method get takes integer unitTypeId returns thistype
return thistype.table[unitTypeId]
endmethod
static method has takes integer unitTypeId returns boolean
return thistype.table.has(unitTypeId)
endmethod
private static method onInit takes nothing returns nothing
set thistype.table = Table.create()
endmethod
endstruct
/*
private static IntLinedList list
integer unitTypeId
integer languageId
integer normalLimit
static method create takes integer unitTypeId, integer languageId returns thistype
local thistype this = .allocate()
set this.unitTypeId = unitTypeId
set this.languageId = languageId
set this.normalLimit = -1 // unlimited
//call thistype.list.add(list.size(), this)
call this.refresh()
return this
endmethod
method onPlayerLanguageChange takes player p, integer newPlayerLanguage returns nothing
if this.languageId != newPlayerLanguage then
call SetPlayerTechMaxAllowedSwap(unitTypeId, 0, p)
else
call SetPlayerTechMaxAllowedSwap(unitTypeId, this.normalLimit, p)
endif
endmethod
method refresh takes nothing returns nothing
local integer i = 0
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
set i = i + 1
endloop
endmethod
static method getAll takes nothing returns IntLinkedList
return thistype.list
endmethod
private static method onInit takes nothing returns nothing
set thistype.list = IntLinkedList.create()
endmethod
endstruct
function BindUnitTypeToLanguage takes integer unitTypeId, integer languageId returns nothing
endfunction
*/
struct Strings
private static TableArray tableArray
private static Table strToIndex
private static integer array playerLanguage
private static integer defaultLanguage
private static Table table
private static player array p
private static method onLanguageChange takes player p returns nothing
endmethod
static method setPlayerLanguage takes player p, integer languageId returns nothing
set thistype.playerLanguage[GetPlayerId(p)] = languageId
set udg_languageChange = 1.
set udg_languageChange = 0.
endmethod
static method getPlayerLaguage takes player p returns integer
return thistype.playerLanguage[GetPlayerId(p)]
endmethod
static method setDefaultLanguage takes integer languageId returns nothing
set thistype.defaultLanguage = languageId
endmethod
static method load takes player p, integer stringId returns string
local string s = thistype.tableArray[thistype.playerLanguage[GetPlayerId(p)]].string[stringId]
if s == null then
set s = thistype.tableArray[thistype.defaultLanguage].string[stringId]
endif
return s
endmethod
static method remove takes integer stringId, integer languageId returns nothing
call thistype.tableArray[languageId].remove(stringId)
endmethod
static method save takes integer stringId, integer languageId, string text returns nothing
set thistype.tableArray[languageId].string[stringId] = text
set thistype.strToIndex[StringHash(text)] = languageId
endmethod
static method getStringId takes string text returns integer
return thistype.strToIndex[StringHash(text)]
endmethod
/*
UNIT -- TO BE MOVED
*/
static method setUnitTypeName takes integer unitTypeId, integer stringId returns nothing
local UnitTranslator trans
if ItemTranslator.has(unitTypeId) then
set trans = ItemTranslator.get(unitTypeId)
else
set trans = ItemTranslator.create(unitTypeId)
endif
set trans.unitNameId = stringId
endmethod
static method attachUnitTypeToBaseUnitType takes integer baseUnitTypeid, integer languageId, integer subUnitTypeId returns nothing
endmethod
static method bindUnitTypeToIndex takes integer unitTypeId, integer stringId returns nothing
set thistype.table.integer[unitTypeId] = stringId
endmethod
static method getUnitTypeIndex takes integer unitTypeId returns integer
return thistype.table.integer[unitTypeId]
endmethod
//! runtextmacro HANDLE_WRAPPER("BlzSetUnitName", "UnitName", "unit")
//! runtextmacro HANDLE_WRAPPER("BlzSetHeroProperName", "HeroProperName", "unit")
//! runtextmacro HANDLE_WRAPPER("BlzSetItemName", "ItemName", "item")
//! runtextmacro HANDLE_WRAPPER("BlzSetItemDescription", "ItemDescription", "item")
//! runtextmacro HANDLE_WRAPPER("BlzSetItemTooltip", "ItemTooltip", "item")
//! runtextmacro HANDLE_WRAPPER("BlzSetItemExtendedTooltip", "ItemExtendedTooltip", "item")
//! textmacro_once HANDLE_WRAPPER takes NATIVE_CODE, API_CODE, TYPE
static method setPlayer$API_CODE$ takes player p, $TYPE$ t, integer stringId returns nothing
local string s = thistype.load(p, stringId)
if s != null and p == GetLocalPlayer() then
call $NATIVE_CODE$(t, s)
endif
endmethod
static method set$API_CODE$ takes $TYPE$ t, integer stringId returns nothing
local integer i
if stringId == 0 then
return
endif
set i = 0
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call thistype.setPlayer$API_CODE$(thistype.p[i], t, stringId)
set i = i + 1
endloop
endmethod
//! endtextmacro
//! runtextmacro ITEM_TYPE("Name", "nameId")
//! runtextmacro ITEM_TYPE("Description", "descriptionId")
//! runtextmacro ITEM_TYPE("Tooltip", "tooltipId")
//! runtextmacro ITEM_TYPE("ExtendedTooltip", "extendedTooltipId")
//! textmacro_once ITEM_TYPE takes NAME, VAR
static method setItemType$NAME$ takes integer itemTypeId, integer stringId returns nothing
local ItemTranslator trans
if ItemTranslator.has(itemTypeId) then
set trans = ItemTranslator.get(itemTypeId)
else
set trans = ItemTranslator.create(itemTypeId)
endif
set trans.$VAR$ = stringId
endmethod
//! endtextmacro
static method translateItemByType takes item it returns nothing
local ItemTranslator trans = ItemTranslator.get(GetItemTypeId(it))
if trans == 0 then
return
endif
if trans.nameId != 0 then
call thistype.setItemName(it, trans.nameId)
endif
if trans.descriptionId != 0 then
call thistype.setItemDescription(it, trans.descriptionId)
endif
if trans.tooltipId != 0 then
call thistype.setItemTooltip(it, trans.tooltipId)
endif
if trans.extendedTooltipId != 0 then
call thistype.setItemExtendedTooltip(it, trans.extendedTooltipId)
endif
endmethod
static method translatePlayerItemByType takes player p, item it returns nothing
local ItemTranslator trans = ItemTranslator.get(GetItemTypeId(it))
if trans == 0 then
return
endif
if trans.nameId != 0 then
call thistype.setPlayerItemName(p, it, trans.nameId)
endif
if trans.descriptionId != 0 then
call thistype.setPlayerItemDescription(p, it, trans.descriptionId)
endif
if trans.tooltipId != 0 then
call thistype.setPlayerItemTooltip(p, it, trans.tooltipId)
endif
if trans.extendedTooltipId != 0 then
call thistype.setPlayerItemExtendedTooltip(p, it, trans.extendedTooltipId)
endif
endmethod
private static method onUnitEnter takes nothing returns boolean
local unit u = GetTriggerUnit()
call thistype.setUnitName(u, thistype.getUnitTypeIndex(GetUnitTypeId(u)))
set u = null
return false
endmethod
private static method iterAllUnits takes nothing returns nothing
local unit u = GetEnumUnit()
call thistype.setUnitName(u, thistype.getUnitTypeIndex(GetUnitTypeId(u)))
set u = null
endmethod
private static method delayedSetup takes nothing returns nothing
local timer t = GetExpiredTimer()
local trigger trgUnitEnter = CreateTrigger()
local rect world
call DestroyTimer(t)
set t = null
static if LIBRARY_WorldBounds then
set world = WorldBounds.world
else
set world = GetEntireMapRect()
endif
call TriggerRegisterEnterRectSimple(trgUnitEnter, world)
call TriggerAddCondition(trgUnitEnter, Condition(function thistype.onUnitEnter))
set bj_wantDestroyGroup = true
call ForGroup(GetUnitsInRectAll(world), function thistype.iterAllUnits)
endmethod
private static method onInit takes nothing returns nothing
local integer i = 0
local timer t = CreateTimer()
call TimerStart(t, 0.1, false, function thistype.delayedSetup)
set t = null
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
set thistype.p[i] = Player(i)
call thistype.setPlayerLanguage(thistype.p[i], DEFAULT_LANGUAGE)
set i = i + 1
endloop
set thistype.strToIndex = Table.create()
set thistype.tableArray = TableArray[0x2000]
call thistype.setDefaultLanguage(DEFAULT_LANGUAGE)
set thistype.table = Table.create()
endmethod
endstruct
endlibrary
Example:
-
OnLanguageChange
-
Events
-
Dialog - A dialog button is clicked for languageDialog
-
-
Conditions
-
Actions
-
Set clickedDialog = (Clicked dialog button)
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
clickedDialog Equal to langaugeDialogButton[0]
-
-
Then - Actions
-
Custom script: call Strings.setPlayerLanguage(GetTriggerPlayer(), EN)
-
Trigger - Run UpdateLanguage <gen> (ignoring conditions)
-
Skip remaining actions
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
clickedDialog Equal to langaugeDialogButton[1]
-
-
Then - Actions
-
Custom script: call Strings.setPlayerLanguage(GetTriggerPlayer(), SE)
-
Trigger - Run UpdateLanguage <gen> (ignoring conditions)
-
Skip remaining actions
-
-
Else - Actions
-
-
-
-
ShowLanguagePicker
-
Events
-
Player - Player 1 (Red) skips a cinematic sequence
-
-
Conditions
-
Actions
-
Dialog - Show languageDialog for Player 1 (Red)
-
-
JASS:
scope UpdateLanguage initializer Init
private function IterAllUnits takes nothing returns nothing
local unit u = GetEnumUnit()
call Strings.setUnitName(u, Strings.getUnitTypeIndex(GetUnitTypeId(u)))
// check if its a hero and then try to update hero name...
// check if its carrying items... and then try updating the items...
set u = null
endfunction
private function Update takes nothing returns nothing
local player p = GetTriggerPlayer()
call BJDebugMsg(Strings.load(p, ID_LANG_CHANGED) + I2S(Strings.getPlayerLaguage(p)))
// This must be done again every time the language changes
call Strings.setPlayerAbilityActivatedTooltip(p, 'AHhb', 3, 1)
call Strings.setPlayerAbilityActivatedTooltip(p, 'AHhb', 3, 1)
call Strings.setPlayerAbilityActivatedExtendedTooltip(p, 'AHhb', 4, 1)
call Strings.setPlayerAbilityExtendedTooltip(p, 'AHhb', 5, 1)
call Strings.setPlayerAbilityExtendedTooltip(p, 'AHhb', 11, 2)
call Strings.setPlayerAbilityExtendedTooltip(p, 'AHhb', 12, 3)
call Strings.setPlayerAbilityResearchTooltip(p, 'AHhb', 6, 1)
call Strings.setPlayerAbilityResearchExtendedTooltip(p, 'AHhb', 7, 1)
call Strings.setPlayerAbilityTooltip(p, 'AHhb', 8, 1)
call Strings.setPlayerAbilityTooltip(p, 'AHhb', 9, 2)
call Strings.setPlayerAbilityTooltip(p, 'AHhb', 10, 3)
set bj_wantDestroyGroup = true
call ForGroup(GetUnitsInRectAll(GetEntireMapRect()), function IterAllUnits)
// Pick all items laying about and update them also...
endfunction
private function Init takes nothing returns nothing
set gg_trg_UpdateLanguage = CreateTrigger()
call TriggerAddAction(gg_trg_UpdateLanguage, function Update)
endfunction
endscope
JASS:
library Strings initializer Init uses Language
globals
constant integer ID_LANG_CHANGED = 15
constant integer ID_HELLO = 16
constant integer ID_GENERIC = 17
endglobals
private function OnTime takes nothing returns nothing
call BJDebugMsg(Strings.load(Player(0), ID_HELLO))
call BJDebugMsg(Strings.load(Player(0), ID_GENERIC))
endfunction
private function Init takes nothing returns nothing
local timer t = CreateTimer()
call Strings.save(ID_HELLO, EN, "Hello there")
call Strings.save(ID_HELLO, SE, "Hej där")
call Strings.save(ID_GENERIC, EN, "Just a generic text")
call Strings.save(ID_GENERIC, SE, "Endast en generisk text")
// Footmen test
call Strings.save(2, EN, "Footman")
call Strings.save(2, SE, "Fotsoldat")
call Strings.save(2, SE, "Fotsoldat")
call Strings.bindUnitTypeToIndex('hfoo', 2)
call Strings.save(3, EN, "BlzSetAbilityActivatedTooltip")
call Strings.save(3, SE, "BlzSetAbilityActivatedTooltip")
call Strings.save(4, EN, "BlzSetAbilityActivatedExtendedTooltip")
call Strings.save(4, SE, "BlzSetAbilityActivatedExtendedTooltip")
call Strings.save(5, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA1> or deal half damage to an enemy Undead unit.")
call Strings.save(5, SE, "Ett heligt ljus som kan återställa <AHhb,DataA1> hälsa eller skada en levande död fiende hälften så mycket.")
call Strings.save(6, EN, "Learn Holy Ligh|cffffcc00t|r - [|cffffcc00Level %d|r]")
call Strings.save(6, SE, "Lär Helig|cffffcc00t|r ljus - [|cffffcc00Nivå %d|r]")
call Strings.save(7, EN, "A holy light that can heal a friendly living unit or damage an enemy Undead unit. |n|n|cffffcc00Level 1|r - Heals for <AHhb,DataA1> hp. |n|cffffcc00Level 2|r - Heals for <AHhb,DataA2> hp. |n|cffffcc00Level 3|r - Heals for <AHhb,DataA3> hp. ")
call Strings.save(7, SE, "THIS IS NOT SHOWING")
/*
Ett heligt ljus som kan heala en allierad levadande enhet eller åsamka skada till en levande död. |n|n|cffffcc00Nivå 1|r - Healar <AHhb,DataA1> hp. |n|cffffcc00Nivå 2|r - Healar <AHhb,DataA2> hp. |n|cffffcc00Nivå 3|r - Healar <AHhb,DataA3> hp.
*/
call Strings.save(8, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 1|r]")
call Strings.save(8, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 1|r]")
call Strings.save(9, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 2|r]")
call Strings.save(9, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 2|r]")
call Strings.save(10, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 3|r]")
call Strings.save(10, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 3|r]")
call Strings.save(11, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA2> or deal half damage to an enemy Undead unit.")
call Strings.save(11, SE, "Ett heligt ljus som kan återställa <AHhb,DataA2> hälsa eller skada en levande död fiende hälften så mycket.")
call Strings.save(12, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA3> or deal half damage to an enemy Undead unit.")
call Strings.save(12, SE, "Ett heligt ljus som kan återställa <AHhb,DataA3> hälsa eller skada en levande död fiende hälften så mycket.")
call Strings.save(ID_LANG_CHANGED, EN, "Language changed to: ")
call Strings.save(ID_LANG_CHANGED, SE, "Språk ändrat till: ")
call TimerStart(t, 8.0, true, function OnTime)
endfunction
endlibrary
Not sure exactly how to handle the HeroProperNames, though one can use the wrapper method to override the Object editor proper name.
If you happen to have an idea of why the extended research tooltip is being clipped do tell.
Finally, do post the natives that let you change labels of unit/items being sold/trained, can't seem to find them.
Attachments
Last edited: