First of all some basic information in order to guard against misunderstandings:
There is a Bug with the Random Events Yes/No-button (-> Options).
Example: For savegame A (started/saved with random events = yes), after start of game B (with random events = no), you have to push: Options -> Random Events: yes and no in order to disable R-events in game A. The initial display (=no from last game B) is wrong.
Also note ini-setting Borg=ON + RandomEvents=OFF won't work (i.e. no Borg in game).
Editing monsters is treated in this topic, monster planet attacks & Borg copies here and there's also a topic about Domination victory conditions (Borg).
System bonuses for high morale (loyal/fanatic -> sub_43F66C), penalties for low morale (rebellious/defiant -> sub_43FB90) and also Intel events are not part of the random events. However, there is a confusing overlapping regarding rebellions (and also terrorism) which is discussed in here (default limitation for rebellions in sub_43FB90: systems held 2+; home system seems possible).
Case/type of random event = bitmask means no event, but event bit set at [systInfo 304-307h] resp. [empsInfo 1B0-1B3h].
Aside of loc_44BFA6 (see section 1.1) the bitmask is an unused feature to prevent the recurrence of events.
Also, some events are just paraphrased (e.g. Credits gained). For faithful ingame text, search for the event code in sub_463A78 (resp. sub_462600) then watch for the call of sub_48B2E0 (load lexicon.dic entry number[eax]).
And yes, this topic requires (dis)assembler basics to make effective use of it (use google for tools & tutorials)
Let's get started.
Subroutine 44BEA4 (IncidentProcessTurn) divides the random events into 3 groups:
- 1.) System-specific events (sub_44BF30 ->sub_44C04C)
2.) Monster & global events (sub_44CBE0)
3.) Empire-specific non-system events (sub_44C860)
Defaults: 45 / 35 / 30 / 20 / 10 ; (same defaults for group 1, but separate values, see 1.3)
Time delay factor (turns) for difficulty levels 1-5 (5=impossible):
Code: Select all
44BECC mov edx, 2Dh
44BEE1 mov edx, 23h
44BEA6 mov edx, 1Eh
44BEF6 mov edx, 14h
44BF0B mov edx, 0Ah
An example how to turn off whole groups of events:
Code: Select all
asm-address /hex-code /instruction -> replacement
-> no system events:
44BF30 53 push ebx -> C3 (retn)
-> no Monster & global events:
44BF20 E8BB0C0000 CALL 44CBE0 -> 9090909090
-> no empire-specific non-system events:
44BF25 E836090000 CALL 44C860 -> 9090909090
1. System-specific events
Sub_44BF30 reads a sort of empire province list (address from [EmpsInfo+50h]) to determine valid system-IDs for random events, i.e. uninhabitated & independent (free minor / rebel) systems are not scheduled. There's a small chance that bugs are hiding in this obfuscated code, though.
1.1 Base chance for system events
At the global loop, the default chance for system events (per turn for each empire) depends on event group 3 bitmask [empsInfo+1B0h]. If there is no entry yet then sub_44C04C will be called (no random 100%) else the chance is reduced to 60%.
Adjusting frequencies in BotF:
The divisor forces the output (i.e. the used remainder in edx) of random integer to the range 0 - [divisor-1].
Consequently, the event probability is given by the inclusion-exclusion check of the specific events (resp. exit-check for all events) vs. the divisor.
As a first example, we take the global chance for system events.
Once event (group 3 !) entry for that empire THEN 40% chance for no system events each turn:
Code: Select all
edi = 0 / eax = [empsInfo+1B0h]
44BFB1 cmp edi, eax // if event group-3 bitmask = 0
44BFB3 jz short loc_44BFD2 (skip random generator)
ELSE
44BFC3 mov ecx, 0Ah (divisor 10)
Range of random integer [remainder in edx] is 0-9
44BFCD cmp edx, 4 // no events if 0-3
44BFD0 jb short loc_44BF72 (no system events for this major)
1.2 Initial test for rebellions (-> the only random event without time delay factor!)
IF no home system, but rebellious and not last system held THEN there is a chance for a rebellion (depending on current morale value):
Code: Select all
44C077 test ah, ah // if home system
44C079 jz loc_44C18A (no rebellion / continue)
44C085 cmp eax, 0FFFFFFFCh // if not rebellious
44C088 jnz loc_44C18A (no rebellion / continue)
- Random Generator -
44C08E cmp word ptr [ebx+44h], 0Ah // minimum divisor 10
44C125 mov dword ptr [esp+10h], 0Ah // minimum divisor 10
44C0B7 idiv dword ptr [esp+10h] (divisor = morale value or minimum)
44C0BB cmp edx, 1 // default chance 1:[morale value or minimum]
44C0BE jnz loc_44C18A (no rebellion / continue)
44C0EE cmp eax, 1 // number of systems held
44C0F1 jle loc_44C18A (no rebellion / continue)
1.3 System-events time delay (turns) (difficulty levels 1-5)
Code: Select all
44C1A3 mov ecx, 2Dh
44C1C4 mov ecx, 23h
44C1CB mov ecx, 1Eh
44C1D2 mov ecx, 14h
44C1D9 mov ecx, 0Ah
1.4 Event frequencies for each difficulty level (loc_44C1E0)
The random engine for the following system events takes its divisor (dynamic range of remainder ->frequency) from data field ds:58FD58 depending on difficulty level.
Data field in trek.exe at 0x18DB5C: (actual used values i.e. not 0x18DB58!)
Code: Select all
34 08 00 00 D0 07 00 00 08 07 00 00 72 06 00 00 E8 03 00 00
Remainder [edx] = Event type: ( IF larger 0xD THEN no event / exit )
Code: Select all
44C014 switch/jump table: (case / 4byte address of code sequence)
0: bitmask
1: Rebellion (rebellious, defiant or disgruntled - even if last system held! but no home)
2: Comet Strike
3: Super Nova (if red star but no home system)
4: Terrorism (1 Building)
5: Earthquake
6: Planet Shift (positive)
7: Planet Shift (negative)
8: bitmask
9: bitmask
A: Plague
B: Immigration Boom
C: bitmask
D: Energy Meltdown
2. Monster & Global events (subspace anomaly & trade guild strike)
The structure of subroutine 44CBE0 is quite confusing due to two additional delay timer and the hidden F6-key monster feature which can trigger a 'monster opportunity' i.e. BotF tries to add a monster to the map (not necessarily with success, limitations see 2.4). The 3 paths are:
2.1 the F6-key (reference bit in ds:5A2B6C)
F6 = monster opportunity at the end of that turn, assuming random events=yes & delay time is over, else first turn if both conditions are met. E.g. impossible/unmodded the earliest monster opportunity is at turn 11, i.e. soonest at beginning of turn 12 a monster on the map.
The same limitations apply to this fix for a bitmask interference problem (regarding ds:5A2B6C) of the F6 button with the F11-cheat:
Code: Select all
44CCA8 and dl, 0F8h -> 0x4C0AA set = FB
2.2 the down counter (ds:58FDB0 / default: 75)
Every time subroutine 44CBE0 is called, this counter gets decreased. If it's zero when testing then we have a monster opportunity and ds:58FDB0 gets set to [random value + 50]:
Code: Select all
44CC19 mov ecx, 19h (divisor 25 = random range 0-24)
44CC23 add edx, 32h (+50)
Code: Select all
44CCA3 mov ecx, 29h (41)
2.3 the random generator (additional monster time delay factor)
Test random value (remainder of integer division by 700):
zero => Trade Guild Strike (loc_44CCC2)
1or2 => Subspace Anomaly (loc_44CD22)
3-24 => check additional time delay factor for monster opportunity
Code: Select all
44CC3D mov ecx, 2BCh (divisor 700)
Remainder in [edx]
44CC47 cmp edx, 1Eh // exit if not below 30
ELSE
44CC59 test edx, edx // if zero
44CC5B jz short loc_44CCC2 (Trade Guild Strike)
ELSE
44CC5D cmp edx, 3 // if 1 or 2
44CC60 jl loc_44CD22 (Subspace Anomaly)
ELSE
44CC69 cmp edx, 19h // exit if not below 25
ELSE (for 3-24)
-> Monster time delay factor:
44CC6E cmp ds: 5A2B44(difficulty_level), 3
IF difficulty level lower or equal (1, 2 or 3):
44CD82 cmp ds: 5A2918(Turn_Number), 4Bh (75)
ELSE (4 or 5):
44CC7C cmp ds: 5A2918(Turn_Number), 28h (40)
IF turn number reached THEN monster opportunity ELSE exit.
2.4 Monster Opportunity (sub_44B220)
Can be called with eax [monster race-ID] or [-1] = 1:7 random chance for each mobile monster (i.e. Edo God & Combat Drone are invalid).
Valide monster race IDs are read from ds:5A36D4 via index value.
Code: Select all
index limits (for experimental purposes)
for random monster:
44B35B mov ecx, 0Eh (divisor 14)
44B36B shr ebx, 1 (remainder / 2)
for a given monster race:
44B373 cmp bx, 7
44B377 jb loc_44B25D // test next index
44B37D jmp loc_44B27A // add monster [index 7] -> Bug?
-> should be: jmp loc_44B347 // exit no monster
For Borg the subroutine 44F264 will be used, which is detailed explained in this post.
Otherwise, bitmask ds:5A2B78 checks whether the selected monster has already previously been generated:
Code: Select all
44B27F cmp [esp+eax*2], 24h // borg race-ID
44B284 jz loc_ 44B382 // if borg (-> sub_44F264)
44B28A mov eax, 1
44B28F mov ecx, ebx
44B291 mov edx, ds: 5A2B78
44B297 shl eax, cl
44B299 test edx, eax // if monster previously added
44B29B jnz loc_44B347 // exit no monster
44B2A1 mov ecx, edx
44B2A3 or ecx, eax
44B2A5 mov ds: 5A2B78, ecx // update bitmask
Code: Select all
NAME: Generated Monsters Bitmask Load Game Bug Fix
DESC: Ds:5A2B78 gets mistakenly deleted when loading a saved game.
AUTHOR: Spocks-cuddly-tribble
URL: https://www.armadafleetcommand.com/onscreen/botf/viewtopic.php?p=20540#p20540
>> 0x0004ace5 89 15 78 2b 5a 00
<< 0x0004ace5 90 90 90 90 90 90
# 0044B8E5 NOP
Other limitations: Testing a vanilla/small map, I got 5 monster max, barring borg and static ones. Maybe this has something to do with a mapsize value or a minimum distance to other monsters when adding to the map (like the 1 monster per sector limitation).
3. Empire-specific non-system events (sub_44C860)
For adjusting the overall frequency of events, try changing (each for integer division of random value):
Code: Select all
44C8E5 mov ecx, 320h (divisor 800)
44C8EF cmp edx, 0Bh
IF remainder greater or equal THEN no event, test next empire
ELSE -> new random value
44C905 mov ecx, 14h (divisor 20)
Code: Select all
44C830 switch/jump table: (case / 4byte address of code sequence)
0: bitmask
1: Credits gained
2: Credits lost
3: diplomatic / attitude change
4: bitmask
5: Hull Virus
6: no event (end code for all events / test next empire)
7: bitmask
8: bitmask
9: Research points lost
A: no event
B: Research points gained