Amineri Posted March 26, 2013 Share Posted March 26, 2013 I finally figured out a way to get the number of elapsed days in the tactical game, so stats and such can be adjusted as the game progresses. I think this was the 5th or 6th thing I tried. And, naturally, in the end the solution was pretty simple, once I knew where to look. This method uses the ERecapStats. This is what shows up at the end of the game, telling you when you achieved what little milestone. The master copy of these actually lives in the strategy game, but it also lives in the tactical game in StrategyGameTransport. This shuttles the values over to the tactical game, so various stats can be recorded (such as capturing an outsider, one-shotting an alien, etc). The full enum is here: (quite long) enum ERecapStats{eRecap_None,eRecap_Days,eRecap_Score,eRecap_AvgShotPctCount,eRecap_AvgShotPctSum,eRecap_AvgDmgCount,eRecap_AvgDmgSum,eRecap_AvgNumTurnsCount,eRecap_AvgNumTurnsSum,eRecap_Missions,eRecap_MissionsWon,eRecap_MissionsLost,eRecap_SoldiersLost,eRecap_SoldiersHired,eRecap_OTSTechs,eRecap_FirstColonel,eRecap_FavoriteClass,eRecap_DaysInInfirmary,eRecap_PsiSoldiers,eRecap_FundingCountries,eRecap_CountriesLost,eRecap_ContinentBonuses,eRecap_ContinentsCovered,eRecap_SatellitesLaunched,eRecap_SatellitesLost,eRecap_SecondSatellite,eRecap_ThirdSatellite,eRecap_FirstContinent,eRecap_FundingReceived,eRecap_GreyMarketFunding,eRecap_FCFunding,eRecap_ScientistsCollected,eRecap_EngineersCollected,eRecap_SoldiersCollected,eRecap_UFOsShotDown,eRecap_InterceptorsLost,eRecap_InterceptorsHired,eRecap_FirestormsBuilt,eRecap_EngagementsAborted,eRecap_AimConsumablesUsed,eRecap_DodgeConsumablesUsed,eRecap_TrackConsumablesUsed,eRecap_MoneyEarned,eRecap_MoneySpent,eRecap_AlloysRecovered,eRecap_EleriumRecovered,eRecap_AlloysUsed,eRecap_EleriumUsed,eRecap_Scientists,eRecap_LabsBuilt,eRecap_TechsResearched,eRecap_AvgTechDaysCount,eRecap_AvgTechDaysSum,eRecap_DifferentAliensCaptured,eRecap_Engineers,eRecap_WorkshopsBuilt,eRecap_ItemsBuilt,eRecap_SHIVsBuilt,eRecap_FoundryTechs,eRecap_MedikitsBuilt,eRecap_ArcThrowersBuilt,eRecap_FacilitiesBuilt,eRecap_FacilitiesRemoved,eRecap_MaxPower,eRecap_MaxAdjacencies,eRecap_ObjInterrogateAlien,eRecap_ObjResearchOutsiderShards,eRecap_ObjCollectShards,eRecap_ObjAssaultBase,eRecap_ObjBuildHyperwave,eRecap_ObjRecoverPsiLink,eRecap_ObjBuildGollop,eRecap_ObjChooseVolunteer,eRecap_FirstPsi,eRecap_FirstAlienWeapon,eRecap_FirstFirestorm,eRecap_FirstCarapace,eRecap_FirstLaser,eRecap_FirstTitan,eRecap_FirstBlaster,eRecap_FirstSkeleton,eRecap_FirstPsiArmor,eRecap_FirstGhost,eRecap_FirstIntLaser,eRecap_FirstIntPlasma,eRecap_FirstIntEMP,eRecap_FirstIntFusion,eRecap_DaysSurvived,eRecap_FirstMissionLost,eRecap_FirstMissionLostDays,eRecap_FirstFiveSoldierMission,eRecap_FirstSixSoldierMission,eRecap_SoldierNamesCustomized,eRecap_SoldierLooksCustomized,eRecap_MaxSoldiers,eRecap_MaxLeveledSoldiers,eRecap_StartingContinent,eRecap_OneShotKills,eRecap_AIOneShotKills,eRecap_AliensKilled,eRecap_AliensKilledFromExplosives,eRecap_ContinentsWon,eRecap_FirestormLaunch,eRecap_InterceptorLaunch,eRecap_GamesSubmitted,eRecap_IronmanGames,eRecap_TutorialGames,eRecap_SnipersOnMissions,eRecap_AssaultsOnMissions,eRecap_HeaviesOnMissions,eRecap_SupportsOnMissions,eRecap_UplinksBuilt,eRecap_NexiiBuilt,eRecap_ThermoBuilt,eRecap_EleriumPowerBuilt,eRecap_GeneratorBuilt,eRecap_NorthAmericaStarts,eRecap_SouthAmericaStarts,eRecap_AfricaStarts,eRecap_EuropeStarts,eRecap_AsiaStarts,eRecap_NorthAmericaBonuses,eRecap_SouthAmericaBonuses,eRecap_AfricaBonuses,eRecap_EuropeBonuses,eRecap_AsiaBonuses,eRecap_MAX}; Field 1 is eRecap_Days, which is just the number of days elapsed in the game until you won. It is normally only set in the function YouWin() ((I'm not kidding about the function name)). I simply added a call setting the value any time a new mission is created. It will still be set at the end upon winning, so this doesn't even break that recap stat. I did this by replacing the code that avoids randomly assigning mission names on the tutorial mission, putting the stat update in there. The new code looks like: STAT_SetStat(1, Game().GetDays()); kTag.StrValue0 = m_aFirstOpName[Rand(53)]; kTag.StrValue1 = m_aSecondOpName[Rand(76)]; return class'XComLocalizer'.static.ExpandString(m_strOpRandom);The hex replacement is: (applied to XcomStrategyGame.upk) GenerateOpNameBefore total hex (header + function)60 37 00 00 AB 1F 00 00 00 00 00 00 5D 37 00 00 00 00 00 00 00 00 00 00 60 37 00 00 00 00 00 00 07 01 00 00 90 1B 00 00 86 01 00 00 06 01 00 00 49 02 00 28 15 07 22 00 9A 01 35 37 00 00 2C 08 16 04 01 27 37 00 00 06 7B 01 0F 00 5E 37 00 00 2E 71 FE FF FF 19 19 2E 94 FE FF FF 12 20 FD FE FF FF 0A 00 3A FB FF FF 00 1C 91 FD FF FF 16 09 00 09 FB FF FF 00 01 09 FB FF FF 13 00 A4 FA FF FF 00 1B B6 0D 00 00 00 00 00 00 1F 58 47 50 61 72 61 6D 00 16 07 F7 00 2D 00 60 37 00 00 0F 19 00 5E 37 00 00 09 00 EF F9 FF FF 00 01 EF F9 FF FF 1A 2C 2E 01 29 37 00 00 0F 19 00 5E 37 00 00 09 00 EE F9 FF FF 00 01 EE F9 FF FF 1A 2C 06 01 28 37 00 00 06 51 01 0F 19 00 5E 37 00 00 09 00 EF F9 FF FF 00 01 EF F9 FF FF 1A A7 2C 35 16 01 29 37 00 00 0F 19 00 5E 37 00 00 09 00 EE F9 FF FF 00 01 EE F9 FF FF 1A A7 2C 4C 16 01 28 37 00 00 04 12 20 8C FE FF FF 14 00 09 FA FF FF 00 1B 39 0D 00 00 00 00 00 00 01 26 37 00 00 4A 16 04 3A 5F 37 00 00 53 After total hex (header + function)60 37 00 00 AB 1F 00 00 00 00 00 00 5D 37 00 00 00 00 00 00 00 00 00 00 60 37 00 00 00 00 00 00 07 01 00 00 90 1B 00 00 66 01 00 00 06 01 00 00 49 02 00 28 15 07 22 00 9A 01 35 37 00 00 2C 08 16 04 01 27 37 00 00 06 5B 01 0F 00 5E 37 00 00 2E 71 FE FF FF 19 19 2E 94 FE FF FF 12 20 FD FE FF FF 0A 00 3A FB FF FF 00 1C 91 FD FF FF 16 09 00 09 FB FF FF 00 01 09 FB FF FF 13 00 A4 FA FF FF 00 1B B6 0D 00 00 00 00 00 00 1F 58 47 50 61 72 61 6D 00 16 1B CE 26 00 00 00 00 00 00 24 01 19 1B 4C 0E 00 00 00 00 00 00 16 0A 00 82 41 00 00 00 1B F2 0E 00 00 00 00 00 00 16 16 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0F 19 00 5E 37 00 00 09 00 EF F9 FF FF 00 01 EF F9 FF FF 1A A7 2C 35 16 01 29 37 00 00 0F 19 00 5E 37 00 00 09 00 EE F9 FF FF 00 01 EE F9 FF FF 1A A7 2C 4C 16 01 28 37 00 00 04 12 20 8C FE FF FF 14 00 09 FA FF FF 00 1B 39 0D 00 00 00 00 00 00 01 26 37 00 00 4A 16 04 3A 5F 37 00 00 53 This makes the Days Recap stat available in the tactical game. To access the variable, the following call is made: XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle.STAT_GetStat(1);This only works from within XcomGame.upk -- but anywhere in there it will work. The hex for this code snippet is: 19 19 2E 64 2D 00 00 19 12 20 4F FE FF FF 0A 00 D8 F9 FF FF 00 1C F6 FB FF FF 16 09 00 98 F9 FF FF 00 01 98 F9 FF FF 09 00 71 2D 00 00 00 01 71 2D 00 00 0C 00 CF 9D 00 00 00 1B 3E 67 00 00 00 00 00 00 24 01 16This will retrieve the Days recap stat (i.e. the total number of days elapsed since the game began). Testing done so far:1) Updating GenerateOpName and running a mission generates no adverse effects2) Hex code to retrieve Day stat decompiles correctly Additional testing is needed for sure, so please give me any feedback you have, and I'll keep updating here. Link to comment Share on other sites More sharing options...
Amineri Posted March 26, 2013 Author Share Posted March 26, 2013 I've worked out an initial plan on how I want to use the time info, regen, and Damage Reduction. However, before I implement it I want to get a bit of feedback about usability, etc. My plan is to take over the BalanceMods_Easy fields to customize unit regeneration, damage reduction, and bonus HP based on time. Easy Difficulty will get the same balance mods as Normal (not that there is much difference ... just 1 bonus hp to XCOM soldiers in easy). Besides, I figure people who play on easy won't be likely to be playing any mod with this in it :devil: A typical BalanceMod entry looks like: BalanceMods_Easy=(eType=eChar_Sectoid, iDamage=0, iCritHit=0,iAim=0,iDefense=0,iHP=0, iMobility=0,iWill=0)with 7 parameters. Here is the proposed use of these parameters:iMobility - start time (measured in days from March 1, 2015) when unit begins adjusting DR and/or regen (as applicable based on eAbility being set) REGENiDamage - base amount of regen unit begins withiCritHit - rate at which unit gains points of regen (unit type will gain 1 point of regen every iCritHit days) DRiAim - base amount of DR unit begins withiDefense - rate at which unit gains points of DR (unit type will gain 1 point of DR every iDefense days) maxHPiHP - rate at which unit gains points of maxHP (unit type will gain 1 point of maxHP every iHP days)iWill - start time when unit begins adjusting maxHP -- set this very large to make units never gain bonus maxHP Additionally, units will only gain regeneration if they have the eAbility_ReanimateAlly set,and units will only gain DR if they have the eAbility_ReanimateEnemy set There is also some room for custom code for particular units, but I thought this would cover most circumstances and be fairly easily configurable. Feedback, please :) Link to comment Share on other sites More sharing options...
anUser Posted March 27, 2013 Share Posted March 27, 2013 I'm speechless... that is awesome! I'll try to add the function calls and see if something unexpected happens. btw I gave every alien the eAbility_ReanimateAlly and ReanimateEnemy and I haven't noticed anything weird so far, tried several savegames at different stages and it all seemed ok. Those stats seem ok to me but just for one detail, I like damage reduction to be very tied to current HP, kinda they got an unpenetrable armor that once it starts to break it just gets easier to hit them (I'm thinking specialy of sectopods, but it's also good for cyberdisks and drones) I'm currently playgin with DR = current HP / 5, and 20+ starting HP (21-24). Well that's just my preference, my thought here is that if some other value could be used to write the DR formula it'd be great. Maybe should save one value for a cap, or hard code it to limit alien upgrades, at least for certain aliens. Make it so with start time = -1 it never applies, just in case the game gets really long. my 2 cents Link to comment Share on other sites More sharing options...
johnnylump Posted March 27, 2013 Share Posted March 27, 2013 (edited) This really is a great find. I've already got time-sensitive DR and Regeneration into the game based on this method, so it's good to go. One thing I'm concerned about is increasing a unit's max HP, something I tried and failed to do a while back, although you've shown yourself to be far better than I at teasing new capabilities out of the code. In XGUnit are multiple calls to GetUnitMaxHP(), one of those pesky umoddable native functions, and it looks like the game goes there frequently. That may mean anything we do to increase the HP gets overwritten the next time that function is called. One possibility that occurs to me that might simulate increased maximum hit points -- if my math is right -- is proportional Damage Reduction; that is, instead of the standard DR, which reduces all hits by a certain number, it reduces the damage of all hits by, say, 25 percent. It's less than ideal in that an enemy unit's HP provides less info to the player on screen, and rounding might create some outcomes where small-damage weapons aren't actually impacted as much as large-damage ones (I think). We'd also need another free variable in XGUnit to contain this factor. (although possibly not if you're loading this info via the ini route) So current DR: Damage = Damage - DR1Simulated Increased HP DR = Damage = round (Damage * (DR2/100)), where DR2 = 0..100. Addendum:It appears m_iWillCheatBonus is unused in XGUnit and is a potential candidate. Do any other classes extend XGUnit? Edited April 4, 2013 by johnnylump Link to comment Share on other sites More sharing options...
Amineri Posted March 27, 2013 Author Share Posted March 27, 2013 This won't remove or negate the prior code that gives tank-type units DR. This will be another source of it. If a sectopod were given DR using the new method, it would -stack- with the tank-specific DR. What won't be possible (with current plan) is to modify the damage formula for tank-DR as a function of time. In order to make tank-DR apply correctly as a function of current HP, it has to be applied in XGUnit.OnTakeDamage() (so that each time the unit takes damage, the new DR is re-calculated). The generalized DR / Regen I'm discussing here will have the amount calculated at the beginning of each turn. However, the regen will be applied at the end of a unit's turn, while general DR will be applied upon the unit taking damage. There are a lot of possibilities regarding how to scale DR:1) DR applies if unit is in cover (just like the Heavy perk 'Will to Survive')2) DR varies based on current HP3) DR varies based on a hidden variable (basically recharge-able shields, like in XCOM Apoc see:http://www.ufopaedia.org/index.php?title=Personal_Disruptor_Shield)4) DR negates a fixed % of damage done (like how Combat Stims reduce damage by 50%, or Chitin Plating reduces melee by 50%)5) DR scales with time passing in the strategy game The planned system could add a tweak that would allow the unit's DR to go down by 1 (or some fraction of damage dealt) every time the unit takes damage (representing knocking down shields, or whatever). At the start of the unit's turn DR could regenerate based on some sort of formula ... going to max, adding fixed amount, adding based on current hp ... there are several possibilities. --------------------------------------------------------------------------------------------------------------- During my formulation, I started with four variables for time-based DR:a) starting amount (how much DR the unit starts with)b) starting time (when time-based DR started)c) time increment (how often time-based DR increased)d) max DR (a hard cap to how high it could go) Similar reasoning applies to regen, so just for regen + DR that would require 8 variables -- per alien. There are 16 different aliens in the game, so this adds up in a hurry -- now 128 variable values are required. I estimated that hard-coding this in (8 variable values for all 16 aliens) would take somewhere over 1500 hex bytes. Given that the BalanceMod info only provides 7 variables per alien, and that I wanted to add in increasing HP as well (but don't need to adjust starting HP delta), the desired was 11 variables per alien, but only having 7. Sigh ... never as much as a person might want. I could code it so that an alien can't receive both regen AND DR -- then I could use the same variables for each, but they would be interpreted differently based on the eAbility_ReanimateEnemy / eAbility_ReanimateAlly being set. Then I could use the full set of 4 variables for regen OR DR (whichever is specified) and the remaining three variables for scaling up alien HP (allowing start time, hp rate increase, and cap for each alien type's hp). Thoughts on this? The current design doesn't require conditional checks for each alien. Instead the XGUnit.BeginTurn() call loads the values for the specific alien type of the current unit, then computes the regen / DR. This saves a lot of hex-code space (and leaves it for specific alien tweaks that are desired). I did design for a hard-coded cap. However, it would be common across all aliens, although a different hard-cap would apply to regen and DR. I will add a check that if start_time = -1, then no time based adjustments will occur -- excellent suggestion. Link to comment Share on other sites More sharing options...
Amineri Posted March 27, 2013 Author Share Posted March 27, 2013 This really is a great find. I've already got time-sensitive DR and Regeneration into the game based on this method, so it's good to go. One thing I'm concerned about is increasing a unit's max HP, something I tried and failed to do a while back, although you've shown yourself to be far better than I at teasing new capabilities out of the code. In XGUnit are multiple calls to GetUnitMaxHP(), one of those pesky umoddable native functions, and it looks like the game goes there frequently. That may mean anything we do to increase the HP One possibility that occurs to me that might simulate increased maximum hit points -- if my math is right -- is proportional Damage Reduction; that is, instead of the standard DR, which reduces all hits by a certain number, it reduces the damage of all hits by, say, 25 percent. It's less than ideal in that an enemy unit's HP provides less info to the player on screen, and rounding might create some outcomes where small-damage weapons aren't actually impacted as much as large-damage ones (I think). We'd also need another free variable in XGUnit to contain this factor. So current DR: Damage = Damage - DR1Simulated Increased HP DR = Damage = round (Damage * (DR2/100)), where DR2 = 0..100. Hee hee ... I've already found a hook by which the game increases alien HP. It's not based on time, but based on difficulty. The game currently has a method by which changing difficulty part-way through a tactical mission -immediately- adjusts all the alien's stats to match the BalanceMod of the newly selected difficulty. The call chain goes : ChangeDifficulty() >> SetDifficulty >> BuildCharacters >> BuildCharacter >> ModifyStatsByDifficultyThis goes in and resets all the of the TCharacter stat data based on the newly selected difficulty.ChangeDifficulty also calls UpdateUnitStats(), which looks like: foreach AllActors(class'XGUnit', Unit){// End:0x5c Loop:Falseif(!Unit.IsDead()){Unit.UpdateStatsFromGameCore();} } The BalanceMod structures are applied in ModifyStatsByDifficulty. By removing the BalanceMod_Easy assignments there, I can free up enough space to add a time-specific adjustment to HP. To force the update, would call ChangeDifficulty (to the current difficulty). Since I'm hooking into an existing method that retroactively changes ALL alien stats, I feel pretty good about it working :) It's not 100% until it is coded and tested, of course! Link to comment Share on other sites More sharing options...
Recommended Posts