Skip to content

Commit

Permalink
Level up rewrite (#539)
Browse files Browse the repository at this point in the history
* Start

* WIP

* WIP2

* WIP3

* WIP4

* Finish testing Levelup Rewrite

* Update Config.event

* Update Config.event

* Update Definitions.s

---------

Co-authored-by: Veslyquix <[email protected]>
  • Loading branch information
Vesly01 and Veslyquix authored Oct 5, 2023
1 parent 64f329b commit 3396182
Show file tree
Hide file tree
Showing 147 changed files with 92,349 additions and 4 deletions.
21 changes: 19 additions & 2 deletions EngineHacks/Config.event
Original file line number Diff line number Diff line change
Expand Up @@ -359,11 +359,28 @@ MESSAGE Please see the README.md there or ask @Vesly for help.
//If true, the stat colors for player units will reflect their growth in the stat.
#define USE_STAT_COLORS True

//When leveling up, the game rerolls (up to 5 times) until you gain at least this many points
#define MIN_STAT_GAIN 1 // vanilla is 1

//If true, Fixed Growths mode will exist.
// Note: this takes promotion level into account, but does not take stat boosters into account
#define FIXED_GROWTHS_MODE False

//ID of flag to use for fixed growths mode, if enabled
#define FIXED_GROWTHS_FLAG_ID 0xEF
//ID of flag to use for fixed growths mode, if enabled (also works if flag here is defined as 0)
#define FIXED_GROWTHS_FLAG_ID 0xEF

//If true, Stat bracketing mode will exist. This keeps your stats within X points of the average.
// Note: this takes promotion level into account, but does not take stat boosters into account
#define STAT_BRACKETING_EXISTS False

//ID of flag to use for bracketed growths mode, if enabled (also works if flag here is defined as 0)
#define BRACKETED_GROWTHS_FLAG_ID 0xED // 0xEE is used to hide dmg numbers in battle

//If using stat bracketing and your stat is below average by this many points, always gain it on levelup
#define FORCE_WHEN_BELOW_AVERAGE_BY_AMOUNT 3

//If using stat bracketing and your stat is above average by this many points, never gain it on levelup
#define PREVENT_WHEN_ABOVE_AVERAGE_BY_AMOUNT 5

//If true, passive growth boosters and metis tome will not affect growths in fixed growths mode.
#define FIXED_GROWTHS_DONT_BOOST True
Expand Down
26 changes: 26 additions & 0 deletions EngineHacks/Necessary/GrowthGetters/C/AssembleLyn.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@echo off

SET startDir="C:\devkitPro\devkitARM\bin\"
SET as="%startDir%arm-none-eabi-as"
SET LYN="C:\devkitPro\lyn.exe"

@rem Assemble into an elf
%as% -g -mcpu=arm7tdmi -mthumb-interwork %1 -o "%~n1.elf"

if exist "Definitions.s" (

@rem Assemble definitions into a .elf if exists
%as% -g -mcpu=arm7tdmi -mthumb-interwork "Definitions.s" -o "Definitions.elf"

@rem Assebmle into a .lyn.event with definitions
%LYN% "%~n1.elf" "Definitions.elf" > "%~n1.lyn.event"

echo y | del "%~dp0Definitions.elf"
) else (
@rem Assemble into a .lyn.event
%LYN% "%~n1.elf" > "%~n1.lyn.event"
)

echo y | del "%~n1.elf"

pause
297 changes: 297 additions & 0 deletions EngineHacks/Necessary/GrowthGetters/C/CheckBattleUnitLevelUp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
#include "CheckBattleUnitLevelUp.h"

#define regularGrowths 0
#define fixedGrowths 1
#define bracketedGrowths 2

#define hpStat 0
#define strStat 1
#define sklStat 2
#define spdStat 3
#define defStat 4
#define resStat 5
#define lukStat 6
#define magStat 7

// repurpose bwl->moveAmt into bwl->promotionLvl
u8 GetUnitPromotionLevel(struct Unit* unit) // https://github.com/FireEmblemUniverse/fireemblem8u/blob/ba48415eb29806813106e5874969421ea759d507/src/bmsave-bwl.c#L375
{
extern u8 gBWLDataStorage[];
int maxLevel = Class_Level_Cap_Table[unit->pClassData->number];
int uid = unit->pCharacterData->number;
if (uid > 0x45) { return maxLevel; }
int result = *(gBWLDataStorage + 0x10 * (uid - 1) + 8); // repurpose bwl->moveAmt into bwl->promotionLvl
if (result < 10) { return 10; }

if (result > maxLevel) { return maxLevel; }
return result;
}

// repurpose bwl->moveAmt into bwl->promotionLvl
void SetUnitPromotionLevel(struct Unit* unit, int level) // https://github.com/FireEmblemUniverse/fireemblem8u/blob/ba48415eb29806813106e5874969421ea759d507/src/bmsave-bwl.c#L375
{
extern u8 gBWLDataStorage[];
int uid = unit->pCharacterData->number;
if (uid > 0x45) { return; }
gBWLDataStorage[(0x10 * (uid - 1)) + 8] = level; // repurpose bwl->moveAmt into bwl->promotionLvl
}

u8 NewPromoHandler_SetInitStat(struct ProcPromoHandler *proc) // repoint so we also save the unit's promo level
{
proc->stat = PROMO_HANDLER_STAT_INIT;
if (proc->unit) {
SetUnitPromotionLevel(proc->unit, proc->unit->level);
}
return 0;
}


int GetAverageStat(int growth, int stat, struct Unit* unit, int levels) { // unit required because bunit includes stats from temp boosters (eg. weapon provides +5 str) in their raw stats
int result = 0;
int baseStat = GetBaseStatFromDefinition(stat, unit);
result = ((growth * levels) / 100) + baseStat;
return result;

}

int GetStatFromDefinition(int id, struct Unit* unit) { // unit required because bunit includes stats from temp boosters (eg. weapon provides +5 str) in their raw stats
switch (id) {
case hpStat: return unit->maxHP;
case strStat: return unit->pow;
case sklStat: return unit->skl;
case spdStat: return unit->spd;
case defStat: return unit->def;
case resStat: return unit->res;
case lukStat: return unit->lck;
case magStat: return unit->_u3A; // mag
}
return 0;
}

int GetBaseStatFromDefinition(int id, struct Unit* unit) {
switch (id) {
case hpStat: return unit->pCharacterData->baseHP + unit->pClassData->baseHP;
case strStat: return unit->pCharacterData->basePow + unit->pClassData->basePow;
case sklStat: return unit->pCharacterData->baseSkl + unit->pClassData->baseSkl;
case spdStat: return unit->pCharacterData->baseSpd + unit->pClassData->baseSpd;
case defStat: return unit->pCharacterData->baseDef + unit->pClassData->baseDef;
case resStat: return unit->pCharacterData->baseRes + unit->pClassData->baseRes;
case lukStat: return unit->pCharacterData->baseLck; // classes do not have base luck + unit->pClassData->baseLck;
case magStat: return MagCharTable[unit->pCharacterData->number].base + MagClassTable[unit->pClassData->number].base;
}
return 0;
}

int GetMaxStatFromDefinition(int id, struct Unit* unit) { // only used to avoid rerolls
switch (id) {
//case hpStat: return unit->pClassData->maxHP; // classes do not have hp caps
case strStat: return unit->pClassData->maxPow;
case sklStat: return unit->pClassData->maxSkl;
case spdStat: return unit->pClassData->maxSpd;
case defStat: return unit->pClassData->maxDef;
case resStat: return unit->pClassData->maxRes;
//case lukStat: return unit->pClassData->maxLck; // classes do not have luck caps
case magStat: return MagClassTable[unit->pClassData->number].cap;
}
return 255; // doesn't really matter much that you'll still roll for stat ups in hp / luck even if you've capped them
}

int GetNumberOfLevelUps(struct BattleUnit* bu) { // This doesn't really account for trainees, but there isn't much we can do about that
int numberOfLevels = bu->unit.level - 1;
if ((bu->unit.pCharacterData->attributes | bu->unit.pClassData->attributes) & CA_PROMOTED) {
numberOfLevels += GetUnitPromotionLevel(&bu->unit);
}
if (numberOfLevels < 0) return 0; // probably unnecessary
return numberOfLevels;
}

int NewGetStatIncrease(int growth, int mode, int stat, struct BattleUnit* bu, struct Unit* unit) {
int result = 0;
int currentStat = GetStatFromDefinition(stat, unit);
if (GetMaxStatFromDefinition(stat, unit) < currentStat+1) { return 0; } // no point trying to raise a stat if we've hit the caps. This'll improve our rerolled statups when caps have been hit

if (mode == fixedGrowths) {
int averageStat = GetAverageStat(growth, stat, unit, GetNumberOfLevelUps(bu));
while (growth > 100) {
result++;
growth -= 100;
}
if (currentStat < averageStat) {
result++;
}
return result;
}

if (mode == bracketedGrowths) {
int averageStat = GetAverageStat(growth, stat, unit, GetNumberOfLevelUps(bu));
while (growth > 100) {
result++;
growth -= 100;
}
if (currentStat >= (averageStat + PreventWhenAboveAverageBy_Link)) {
return result;
}
if ((currentStat + ForceWhenBelowAverageBy_Link) < averageStat) {
result++;
}
else if (Roll1RN(growth))
result++;
return result;
}



while (growth > 100) {
result++;
growth -= 100;
}

if (Roll1RN(growth))
result++;

return result;
}

extern int (*gGet_Hp_Growth)(struct Unit* unit);
extern int (*gGet_Str_Growth)(struct Unit* unit);
extern int (*gGet_Skl_Growth)(struct Unit* unit);
extern int (*gGet_Spd_Growth)(struct Unit* unit);
extern int (*gGet_Def_Growth)(struct Unit* unit);
extern int (*gGet_Res_Growth)(struct Unit* unit);
extern int (*gGet_Luk_Growth)(struct Unit* unit);
extern int (*gMagGrowth)(struct Unit* unit);

void CheckBattleUnitLevelUp(struct BattleUnit* bu) {
if (CanBattleUnitGainLevels(bu) && bu->unit.exp >= 100) {
int mode = regularGrowths; // default
struct Unit* unit = GetUnit(bu->unit.index); // required because bunit includes stats from temp boosters (eg. weapon provides +5 str) in their raw stats
if (GrowthOptions_Link.FIXED_GROWTHS_MODE) {
if (CheckEventId(GrowthOptions_Link.FIXED_GROWTHS_FLAG_ID) || (!GrowthOptions_Link.FIXED_GROWTHS_FLAG_ID)) {
mode = fixedGrowths;
}
}
if (GrowthOptions_Link.STAT_BRACKETING_EXISTS) {
if (CheckEventId(BRACKETED_GROWTHS_FLAG_ID_Link) || (!BRACKETED_GROWTHS_FLAG_ID_Link)) {
mode = bracketedGrowths;
}
}


int statGainTotal = 0;

bu->unit.exp -= 100;
bu->unit.level++;

if (UNIT_CATTRIBUTES(&bu->unit) & CA_MAXLEVEL10) {
if (bu->unit.level == 10) {
bu->expGain -= bu->unit.exp;
bu->unit.exp = UNIT_EXP_DISABLED;
}
} else if (bu->unit.level >= Class_Level_Cap_Table[bu->unit.pClassData->number]) {
bu->expGain -= bu->unit.exp;
bu->unit.exp = UNIT_EXP_DISABLED;
}

int hpGrowth = gGet_Hp_Growth(&bu->unit);
int strGrowth = gGet_Str_Growth(&bu->unit);
int sklGrowth = gGet_Skl_Growth(&bu->unit);
int spdGrowth = gGet_Spd_Growth(&bu->unit);
int defGrowth = gGet_Def_Growth(&bu->unit);
int resGrowth = gGet_Res_Growth(&bu->unit);
int lukGrowth = gGet_Luk_Growth(&bu->unit);
int magGrowth = 0;
if (gMagGrowth) { magGrowth = gMagGrowth(&bu->unit); }

bu->changeHP = NewGetStatIncrease(hpGrowth, mode, hpStat, bu, unit);
statGainTotal += bu->changeHP;

bu->changePow = NewGetStatIncrease(strGrowth, mode, strStat, bu, unit);
statGainTotal += bu->changePow;

bu->changeSkl = NewGetStatIncrease(sklGrowth, mode, sklStat, bu, unit);
statGainTotal += bu->changeSkl;

bu->changeSpd = NewGetStatIncrease(spdGrowth, mode, spdStat, bu, unit);
statGainTotal += bu->changeSpd;

bu->changeDef = NewGetStatIncrease(defGrowth, mode, defStat, bu, unit);
statGainTotal += bu->changeDef;

bu->changeRes = NewGetStatIncrease(resGrowth, mode, resStat, bu, unit);
statGainTotal += bu->changeRes;

bu->changeLck = NewGetStatIncrease(lukGrowth, mode, lukStat, bu, unit);
statGainTotal += bu->changeLck;

bu->changeCon = NewGetStatIncrease(magGrowth, mode, magStat, bu, unit); // mag uses the changeCon byte (and always has)
statGainTotal += bu->changeCon;

if ((statGainTotal < minStatGain_Link) && (mode != fixedGrowths)) {
for (int attempts = 0; attempts < 5; attempts++) {

// if we did not get atleast x stat ups on level, try each of these in order
// previously you'd often get +1 hp and nothing else on bad level ups because of the order
// so I've changed the order to Str > Mag > Spd > Def > Res > Luk > Hp > Skl
// you're more likely to get a more useful single stat levelup this way
if (!bu->changePow) { // don't count changePow multiple times in statGainTotal
bu->changePow = NewGetStatIncrease(strGrowth, mode, strStat, bu, unit);
statGainTotal += bu->changePow;
if (statGainTotal >= minStatGain_Link)
break;
}
if (!bu->changeCon) {
bu->changeCon = NewGetStatIncrease(magGrowth, mode, magStat, bu, unit); // mag uses the changeCon byte (and always has)
statGainTotal += bu->changeCon;
if (statGainTotal >= minStatGain_Link)
break;
}

if (!bu->changeSpd) {
bu->changeSpd = NewGetStatIncrease(spdGrowth, mode, spdStat, bu, unit);
statGainTotal += bu->changeSpd;
if (statGainTotal >= minStatGain_Link)
break;
}

if (!bu->changeDef) {
bu->changeDef = NewGetStatIncrease(defGrowth, mode, defStat, bu, unit);
statGainTotal += bu->changeDef;
if (statGainTotal >= minStatGain_Link)
break;
}

if (!bu->changeRes) {
bu->changeRes = NewGetStatIncrease(resGrowth, mode, resStat, bu, unit);
statGainTotal += bu->changeRes;
if (statGainTotal >= minStatGain_Link)
break;
}

if (!bu->changeLck) {
bu->changeLck = NewGetStatIncrease(lukGrowth, mode, lukStat, bu, unit);
statGainTotal += bu->changeLck;
if (statGainTotal >= minStatGain_Link)
break;
}

if (!bu->changeHP) {
bu->changeHP = NewGetStatIncrease(hpGrowth, mode, hpStat, bu, unit);
statGainTotal += bu->changeHP;
if (statGainTotal >= minStatGain_Link)
break;
}
if (!bu->changeSkl) {
bu->changeSkl = NewGetStatIncrease(sklGrowth, mode, sklStat, bu, unit);
statGainTotal += bu->changeSkl;
if (statGainTotal >= minStatGain_Link)
break;
}

}
}

CheckBattleUnitStatCaps(GetUnit(bu->unit.index), bu);
}
}


Loading

0 comments on commit 3396182

Please sign in to comment.