# Infinite Heaven — external module contract and addon docs *Shard of mgsv-modding-pack — generated 2026-08-13. 21 files. Provenance is on every entry as `source:path`.* The documented surface for third-party modules: what IH calls on your module, when, and what it expects back. This is the shard to read before writing any new IH mod. ## Infinite Heaven (kapuragu fork) — the framework most TPP script mods target ### `ih_kap:docs/Readme.md` = Infinite heaven = r264 - 2026-05-11 by tin man tex, fork by caplag For MGSV version 1.15 (in title screen), 1.0.15.3 in exe Compatible IHHook version: r17 or later A mod for Metal Gear Solid V: The Phantom Pain intended to extend gameplay through customizable settings and features, as well as providing addon systems to support other mods. Addon systems include allowing custom sidops, missions, enemy soldier types. Has several hundred toggleable options ranging from Subsistence mode for all missions, replay side-ops, Mother base invasions with multiple attack helicopters, Skull attacks in Free roam, Free-cam, skip heli rides, customization of enemy and mother base gear, foot, heavy vehicle and heli patrols in free roam, and much more. It is highly recommended to use IHHook, which is a script extender and graphical menu that has mouse and keyboard support, alongside IH. [url=https://www.nexusmods.com/metalgearsolidvtpp/mods/1226]IHHook on NexusMods[/url] Full Infinite Heaven features and options: [url=http://www.nexusmods.com/metalgearsolidvtpp/articles/5/]nexusmods.com/metalgearsolidvtpp/articles/5/[/url] The most up to date and further documents in MGS_TPP\mod\docs YouTube playlist of demonstrations for many features: [url=https://www.youtube.com/playlist?list=PLSKlVTXYh6F_fmq0u9UmN2XTnfdfcHKJF]youtube.com/playlist?list=PLSKlVTXYh6F_fmq0u9UmN2XTnfdfcHKJF[/url] Recent changes/additions ------------------------------ NOTE: IHHook which adds the imgui menu and other supporting feature to Infinite Heaven has been split to it's own installation and nexus page: https://www.nexusmods.com/metalgearsolidvtpp/mods/1226/ r264 - 2026-05-11 Added hooks for custom result music, add-on mission typing text telop, override for heli force pullout on checkpoint save. Added IHExt back in due to demand. No functionality changed, just recompiled the same code. Fixed sortie prep camera angle when deploying to mission with D-Horse. Fixed a vanilla bug that let players steal knocked-out staff during friendly FOB visits. Actually fixed shift changes on FOBs. r263b - 2026-04-26 Fixed UAV battery drain and shift changes on FOBs. r263a - 2026-04-25 Zeta r20 compatibility issue from r262 fixed. r263 - 2026-04-24 Vehicle reinforcements fix and expansion: fixed an issue where vehicle reinforcements packs weren't being added to the rotation in vanilla game instances (A HERO'S WAY, CURSED LEGACY). This spun off into further fixes of vanilla oversights (setting up the reinforce block soldiers' CP, soldier type and sub-type, applying the vehicle camo by sub-type) and optional additions to the system (an optional reinforce block unload to allow for more reinforce waves in the same session, overriding reinforce vehicle color class) Fixed for the ACC add-on system: sortie camera center position, D-Dog center position. Fixed loading screen crash on non-lethal FOBs with sneaking suits equipped on enemy soldiers. Fixed freeze on module reload. Added add-on mission assault LZ support, with connection to destroyable anti-air radar gimmicks optional. Fixed the primary NONE weapon slot being replaced with an SVG-67 during FOB Free Placement mode. Fixed the Start Offline softlock on bootup. Fixed Bionic Arm upgrade ranks. Minimal settings will now entirely disable the upgrades. Fixed control lock remaining after checkpoints which saved when the Infinite Heaven menu was open are reloaded. Side Ops in Missions can now be enabled in PHANTOM LIMBS. Added Minute and Second setting options in the Time Scale menu. Added Weather Menu to Cutscene Menu. Added back old Quarantine Platform boss events as an option. When enabled, damaging Skulls and the body of the Man on Fire will spawn one of them. The Man on Fire will disappear immediately, however. He's afraid of water. Fixed free camera input still being executed on game pause. r262 - 2023-06-19 Fix: Hang on loading FOB. Not sure about this one, introduced the bug in 261, but checking 262 now bug not happening, so fixed it while reworking something else I guess. IHHook menu can be open when idroid is open, only mouse interaction though. titleMenu: Can open IHHook menu at title for and alternate way to abortToACC. Menu: Halts at top/bottom menus when holding down menu up/down. Repeat rate when using gui menu increased. Boss events: (Boss event menu in mission, Events menu > Boss event menu in ACC) The previous 'Skull attack' system has been reworked. Split into the underlying gameobject types of TppParasite2 (the Armor and Mist parasite skull units in the base game) TppBossQuiet2 (Camo/Sniper parasite Skull units and Quiet in the base game) TppVolgin2 (new for the ih system, Volgin/the man on fire) All boss types can have subTypes (like the base game ones mentioned above) which authors can develop addons for to give new appearances. (See MGS_TPP\mod\bosses\) Like the previous system an event is split into a countdown period randomized between the min and max time setting. Then chooses which boss types and subType for the boss type will be in the event. Then kicks off the actual attack/appearance of the boss(es) Multiple boss types can be included in an event (off by default, may be unstable with other ih features enabled) Only one sub type per boss type will be choosen. HeliSpace (ACC) mission addon support - thanks retali8 for building his own helispace and testing features. API: InfButton: Added some alternate/friendler mask names for some buttons READY_WEAPON for HOLD BINOCULARS for SUBJECT DIVE for EVADE Values are the same so doesn't matter which you use. Fix: GetGamePath not returning IHHook.GetGamePath. - thanks Environ for the related report and log files. Loading: TppMissionList.GetLocationPackagePath,GetMissionPackagePath will warn on missing fpks (according to snakebite.xml) Project (Infinite Heaven repo): Build settings: Extensions renamed to .buildmod Settings updated for mgsv_buildmod 1.1 Infinite Heaven repo\tpp\tpp.code-workspace: A vscode workspace for the dev (Infinite Heaven repo\tpp) folder. See Infinite Heaven repo\vscode.md for more info. tpp/vscode/vscode_hint-*: Generated by WriteVscodeHint. The vscode lua language server extension needs a bit of help making sense of mgsv/IHs setup, these lua files are parsed by the extension to map the files to their global modules (not used by IH mod itself). See Infinite Heaven repo\vscode.md for more info. Debug menu > Vscode menu: "Commands to generate files to support using vscode with a mgsv project." debug_WriteVscodeHintOnTitle - "Runs WriteVscodeHint when loading has reached Title" WriteVscodeHint - "Writes GameDir/mod/vscode/vscode_hint-mod.lua,vscode_hint-base_game.lua , writes a hint files for vscode lua language server extension of mod lua files loaded." debug_WriteVarsOnSave - "Runs WriteVars on IH save (which includes close IH menu)" WriteVars - "Writes vars, svars, gvars, mvars to GameDir/mod/vscode/, for manual perusal or use with vscode" debugMessages: Expanded from Off, On to: OFF, ALL, RECIEVED. ALL (same as pre r262 'On') logs all messages sent. RECIEVED only logs those that have modules subscribed to the message. PrintOnMessage log line now notes recievers of the message (if any). Errors in Message funcs should now directly log the point of the error, with the reciever/module noted in the PrintOnMessage log line after it. r261 - 2023-03-24 'Warp to latest marker' now works when driving vehicle or walker gear. [youtube]FVc0bw9ooFs[/youtube] https://youtu.be/FVc0bw9ooFs Fix: disableHerbSearch possibly not working correctly. Fix: Randomize RouteSets being called despite individual options being off (though not if main setting was off). thanks caplag for a related report. Fix: "Custom soldier type in Free/Mission" resetting to off after game is next restarted. Was broken as of r247 - thanks Spectral for the report and troubleshooting files. Fix: enemy soldier type addons with multiple bodies not working, was broken as of r258 - thanks Spectral for the report. Fix: Some cases of Player Cam hook (FOV) not applying. Fix: InfSoldierFaceAndBody not retaining its data on script reload. Affected Apearance menu > Face option when Filter set to Head fova addons. Fix: Back in the dark ages I changed mvars.ene_soldierIDList itself from its original [cpId][soldierId]=cpDefine index to [cpId][soldierId]=soldierName as the value didnt seem to be used anywhere, and I wanted a lookup. Didn't really end up using it much, it was better to just iterate soldierDefine again, so its been reverted. (and API/dev warning I guess if anyone was using it as its modified form in their scripts) Fix: TppRevenge.SetEnableSoldierLocatorList (actually broken out by IH from _ApplyRevengeToCp) not applying in MB. Don't think it affected anything since it failed in a way that it was still applying revenge for all soliders in a cluster, just that it was applying multiple times (with exact same values). See the NOTE: in SetEnableSoldierLocatorList for my puzzlement about it. Fix: Custom DD female type no longer dependant on Custom DD (male) type being set. Either of them will default to normal DD mb body if setting is OFF. Motherbase menu > 'DD Suit', 'DD Suit female' renamed 'Custom DD type in MB' in line with 'Custom Soldier type in mission/free'. The underlying Ivars already were named customSoldierTypeMB_ALL, so you shouln't have to re-set the setting. AroundCam (FreeCam): When AroundCam on, but with Adjust-mode off, changing settings via will now update AroundCam. Fix: FreeCam hang if on when Abort mission. API: addon missionInfo (See notes in InfMission) clearExistingMissionStartSettings - only for use when overriding a vanilla mission, clears all the mission start data so the following options you set can work cleanly noBoxMissionHeliRoute / NO_ORDER_FIX_HELICOPTER_ROUTE support, requires isNoOrderBoxMission to be set clearMissionStartHeliRoute is to give actual support for NO_HELICOPTER_ROUTE_MISSION_LIST, before it was just riding on the assumption that if the addon author gave a startPos they wouldn't have heli routes/starts set up, this gives the same support as the base game for those missions. isNoOrderBoxMission, likewise giving explicit setting for NO_ORDER_BOX_MISSION - thanks caplag for the suggestions API: LoadExternalModule: Don't reload module if prevModule and not isReload. This was causing the early InfInit LoadExternalModuled modules Ivars,IvarsPersist and InfSoldierFaceAndBody to be reloaded. In the case of InfSoldierFaceAndBody it would loose all its data since those functions were called manually rather than PostAllModulesLoad. API: InfUserMarker.GetMarkerPosition return type changed from Vector3 to {x,y,z} as it's more commonly used through other IH functions. r260 - 2023-03-10 Fix: quest adding their missionPacks in helispace - thanks CapLag for an unrelated report that made me test something related. Fix: Repop of sideop 144 tent_q99040 "Secure the Remains of the Man on Fire" arival on quarantine platform leaving player stuck in helipad. Fix: Infinite load screen when IH startOnFoot used when force go to mb on quest clear > demo play. Another case of the prior fix in r259 for start on foot, which Repop of sideop 144 is also a case of. Fix: Several bugs setting quest flags incorrectly. Could result in some of the hidden/managed quests activating or not. Could also cause the all sideops disabled bug. A manual fix is to run "Reroll sideops selection" with "Repop mode" set to Allways. Ancillary ih saves (addon mission and quest states) are now cleared on new game. InfQuest / IH Sideops menu: [youtube]2UUXDfMfrso[/youtube] https://youtu.be/2UUXDfMfrso Renamed unlockSideOpNumber to quest_forceQuestNumber, just an internal name change to be more consistant and accurate. Though you will have to set the setting again. Renamed ihSideopsPercentageCount to quest_addonsCountForCompletion, just an internal name change to be more consistant and accurate. Though you will have to set the setting again. quest_updateRepopMode="Repop mode": "On none left"|"Allways" Lets you choose the behaviour of how repeatable sideops are refreshed. The update is run for the sideop area of a quest you just finished, or for all areas when changing many of the IH sideops options or rerolling sideops. The default behaviour 'On none left' will only repopulate sideops when there are no other uncompleted sideops, and all other repeatable sideops have been completed. 'Allways' will refresh repeatable quests every time the update is called. quest_setIsOnceToRepop="Repop one-time sideops" "Lets you force story and one-time sideops to be replayable." Replaces "Unlock Sideops mode". quest_selectForArea="Selection for Area mode" Renamed from sideOpsSelectionMode Categories removed, use the Sideops category filter menu instead. 'Random Addon' setting added to prioritize Addon sideops. Sideops are broken into areas to stop overlap, this setting lets you control the choice which repop sideop will be selected to be Active for the area. 'Random Addon' will prioritize Addon sideops first. All selection is still prioritized by uncompleted story sideops, then other uncompleted sideops, then repop sideops selected by this option. "Show on UI menu" Replaces "Show all open sideops" Settings for what sideops to show and how they should be sorted depedending on various parameters for sideops on the idroid sideops list. The vanilla behavior just shows current Active and Cleared sideops, in index order, which lets you see past progression/completion, Though since uncleared sideops do have priority, one will be selected for Active. So if there's multiple uncleared for an area they will not be shown, which gives you less of an idea of future progression. These option give you individual control for showing sideops depending on their conditions. For a given sideop multiple of the underlying conditions may be true at one time and either depend on your progress through the game, or from other IH settings. There is however a limit of 192 entries for the sideop list (there's 157 sideops in the base game), which some settings might push over if you have addon sideops, in which case some Cleared entries be randomly dropped from the list. See the notes for each option for more info. Sorting: Sorting will proceed through all flags that have a sorting setting (not set to None). So sort is in respect to the option above it. None: Will not apply any specific sort, so will just be in index order, but may be moved around if other flags sort it. The other settings, Top, Bottom or Ascending, Descending depending on the flag type, will sort as the settings suggest, but in relation to the prior flags. Since the final sort is by sideop index (the number on the left of the entry in the sideop list) you can use that to see where the list sections from one sorted flag type to the next. quest_uiShow_Active="Active" - "Default is Show. Sideops that are Active are the ones actually currently in play and start when you arrive in the sideop area. Independent of Cleared. You normally wouldn't set this setting to Hide." quest_uiShow_Cleared="Cleared" - "Default is Show. Quests that have been completed." quest_uiShow_Uncleared="Uncleared" - "Quests that have not been completed." quest_uiShow_Activable="Activable" - Only shows those sideops in the selection for being Activated (which includes Active). Usually the best setting to show what sideops are being considered depending on all the underlying conditions and IH settings." quest_uiShow_Open="Open" - "Will try and show all Open sideops, which is usually every sideop as soon as they are introduced through game progression. Most likely to hit the UI limit entries when a lot of addon sideops are installed." quest_uiSort_Active="Sort Active" - Top|Bottom quest_uiSort_Cleared="Sort Cleared" - Top|Bottom quest_uiSort_Uncleared="Sort Uncleared" - Top|Bottom quest_uiSort_Activable="Sort Activable" - Top|Bottom quest_uiSort_Open="Sort Open" - Top|Bottom quest_uiSort_category="Sort by Category" - Ascending|Descending - "The base game sideops are more or less ordered by category already, however addon sideops are added to the end, this sorts by a similar order but puts Story sideops first." quest_uiSort_locationId="Sort by Location" - Ascending|Descending - quest_uiSort_questArea="Sort by sideop area" - Ascending|Descending -"Each main location of the game (Afgh, Africa) is sectioned into about 8 sideops areas to stop overlap and manage loading. You can look at the sideop index to clarify where the list goes from one area to the next (the numbers within an area will be increasing, then be lower for the first sideop in another area). You may want to use in combination with Sort by Location." Sideops selection menu: ivars renamed from sideop_ to quest_category_ Settings changed from OFF, ON to "ALL","NONE","ADDON_ONLY" This lets you have per catergory selection of only Addon sidops. r259 - 2023-02-18 Fix: InfPositions Loadposistions, thanks cap for fix. Fix: ih_mission_states not restoring, thanks cap for report. Fix: Start missions on foot hang on mission end if MB demo triggered. This is an anchient bug that has unfortunately likely been around almost as long as IH itself. Thanks OldBoss for the report and save files. Fix: profiles breaking due to help strings with newlines. thanks EntranceJew for the report. InfLookup: A lot of Message signatures, and general message logging rework from EntranceJew Debugging: Bunch of flow logging to get a better understanding of infinite loading screen, though the answer there is most often just 'the exe is loading stuff and it didn't like something aboout one of the data files' Fix: InfWeather losing addon weather info on script reload, thanks EntranceJew for the report. Fix: appearanceDebugMenu fova ivars fix. They still don't do anything, but they were breaking the menu. And due to the exe crash issue after applying several times it's still of limited use. Thanks retali8 for the report. Fix: 'Warp to last user marker' and Warp body to FreeCam position remove announcelog spam that queues when warping - Thanks SinovialVermin8 for the report Fix: 'Support heli to marker' failing with '#coords.pos~=3' warning. Seems feature was broken as of last version - thanks Dr Solus for the report. InfMotion|Motions menu > motionWarpToOrig|"Warp to original position after play". Since some animations move player position through geometry this may help to recover - thanks caplag for implementation. Mission Addons: various features that were limited to vanilla freeroam now work in addons. IH Saves: ih_mission_states, ih_quest_states, ih_priority_staff now only save if the related features are used. So possibly slight better performance when saving. Existing files will still hang around even if they have no meaningful data in them though. Are also now checked to see if they exist first before loading them, so no potentially confusing error message it used to log when trying to load them reguardless. Debugging and dev stuff: ivars GettSettingText calls wrapped in PCallDebug, and log a warning to make it easier to track down broken functions (and not have them break the menu). Some loading logging, probably won't catch much since most load hangs will be inside exe and lua will just be waiting for TppMission.CanStart which will never come. InfCore.PCall correct multi returns - thanks EntranceJew for the method. PCall and LoadExternalModule announceLogs errors. See Change Log.txt for more detail. Or the github repo commits: https://github.com/TinManTex/InfiniteHeaven/ Disclaimer: ------------------------------ Use the mod at you own risk (which can be mitigated by backing up saves and files replaced by mod) This mod is still largely untested, detailed feedback at Nexus page welcome. Installation ------------------------------ See: Installation.txt file in the Infinite Heaven .zip or [url=http://www.nexusmods.com/metalgearsolidvtpp/articles/2/]http://www.nexusmods.com/metalgearsolidvtpp/articles/2/[/url] Usage: ------------------------------ Opening the Infinite Heaven menu: While in ACC Heli (Safe-space menu), or in-mission (In-mission menu) Press and hold (V key or RStick click) then press (shift key or LStick click) to toggle the mod menu when in the ACC or in-mission. Or hold (Space key or X button) This can be disabled (via IH system menu > Disable hold menu toggle) if it interferes with your play, the above key combo will still work. Also, if IHHook is installed, press F3 key. Using the menu: If you do not have IHHook installed, or IHExt enabled, Infinite Heaven menu will use the Announce Log on the lower left, which is very limited due to its update rate. While the menu it will repeatedly show just the current menu option. Description of menu items: [#]: [Option name] [Effect/ item type symbol] [Setting value or description] Example: 4: Mobility level = 2:Grade 2 Effect symbols: <> Applies change or command when setting is selected/cycled to >> Applies change or command when pressing Menu item type symbols: > Sub-menu >> Command >] Command that closes menu when done Some settings apply when selected or just set the value when the a feature is triggered by another command or during mission load. While menu is open: Use Arrow keys or Dpad to navigate the menu or Hold (V key or RStick click) and press movement keys or Left Stick to navigate the menu. Up/Down to select option. Left/Right to change setting or open sub menu. The size/step of the setting change can be made bigger/smaller by holding or . Press button to reset current setting. Press button to set the current setting to it's minimum. Press button to go to previous menu. By design I try to keep the initial install to all regular game settings and only changed via infinite heavens in-game mod menu. All settings are reset to off on doing a FOB mission. But I suggest you play offline while the mod is installed. Snakebite mod manager allows easy toggling of mods. Quick Menu: A way to quickly trigger certain Infinite Heaven commands. (Must be enabled via option in IH system menu, or by editing InfQuickMenuDefs.lua) When enabled hold the (Q key or Left bumper) button then hold one of the following: (Right mouse or Left Trigger) to warp to last placed usermarker (Left mouse or Right Trigger) to open the menu to heli-to last usermarker (a kludge, but necesary to activate the inter landingzone ride on heli) (E key or Y button) to activate TSM (R key or B button) to activate Free cam (Shift or Left stick click) to activate Static camera (C key or A button) to have Quiet move to last usermarker Profiles: Profiles are lists of settings for IH options, can be used as an alternative, or in conjunction with IHs in-game menu. See MGS_TPP\mod\profiles\All_Options_Example.lua for more info. Settings save file: IH writes its settings to ih_save.lua in the MGS_TPP\mod\saves folder. While the file is editable, editing an inMission save is likely to cause issues. Known Issues ------------------------------ See FAQ Known issues.txt file or [url=http://www.nexusmods.com/metalgearsolidvtpp/articles/6/]http://www.nexusmods.com/metalgearsolidvtpp/articles/6/[/url] Change Log ------------------------------ See: Change Log.txt file in the Infinite Heaven .zip or [url=http://www.nexusmods.com/metalgearsolidvtpp/articles/1/]http://www.nexusmods.com/metalgearsolidvtpp/articles/1/[/url] Thanks: ------------------------------ Kojima Productions for the great game Sergeanur for qartool atvaark for your fox tools ThreeSocks3 for finding the custom text output for Announce log emoose for cracking lua in fpks jRavens for your early testing Shigu for your specific testing and discussions Topher for your great mod manager Snakebite NasaNhak for your voluminous questions and suggestions unknown123 for the MGSV research. sai for FoxLib and further MGSV research. Morbidslinky for creating Side Ops companion and his poking at the quest system as well as his work on Snakebite. Various people for their donations, including: Domenico Jeong Lee Nicholas Gary Joseph Lisa Надежда Ian WolfJack Oliver Daniel Domenico Ryuta Thanks a lot. All the mod users on nexus for trying the mod and bug reports. All the other MGSV mod authors past and current for adding to the community. You for reading this ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addon mission sideop companion workaround.md` Building Sideops Companion sidops for freeroam addon missions that support quests: Set up a sideop, giving proper positions in the map, ditto the icon pos. Set: Quest Map to Afgh Quest Area to whatever the top one is. Quest CP: to anything but NONE Do all the rest normaly, hit build. Open SOC folder \Sideop_Build\GameDir\mod\quests\ lua Change locationId to whatever matches the location: look in mgs_tpp\mod\locations at the locationInfo luas (of location addons you have installed) for the locationId or use the more friendlier TppDefine.LOCATION_ID. ex. TppDefine.LOCATION_ID.GNTN Change areaName to an area defined in the locationInfo questAreas for the same location in this case areaName = "gntn", GOTCHA: you will have to repeat this if you use SOC Build again as it will overwrite the questInfo. Example sideop so you can see the questInfo directly (after installing it, see in mod\quests), or just unzip the .mgsv) and the updated example quest https://mega.nz/file/XI1GXBiY#7SWyMurdNXIXTJgoZvLYhFnPQKgr3Ef6WvMCTuIHtsQ ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addon mission sideop support implementation.md` IH feature quest support for location addons Bug: No sideop location tab for anything but the vanilla locs + All Bug: Cannot select sideop from idroid list to travel to that sideop location. Bug: the sideop started message stuff not showing Needs quest_enemy.fox2 in mission fpkd Can copy vanilla file, for example. No need to edit it. \Assets\tpp\pack\mission2\free\f30010\f30010_fpkd \Assets\tpp\level\mission2\quest\common\quest_enemy.fox2 Needs a quest_block ScriptBlockData Example in: \Assets\tpp\pack\mission2\free\f30020\f30020_fpkd \Assets\tpp\level\mission2\free\f30020\f30020_sequence.fox2 _enemy.lua soldierdefine needs the quest_cp enemies (caplag has already done this for gntn) this.soldierDefine = { ... quest_cp = { "sol_quest_0000", "sol_quest_0001", "sol_quest_0002", "sol_quest_0003", "sol_quest_0004", "sol_quest_0005", "sol_quest_0006", "sol_quest_0007", }, } locationInfo needs questAreas entry Just a single area I centered on the playable area Example in the GNTN locationInfo: --tex defines quest areas for location, see TppQuestList.questList questAreas={ { areaName="prison",--tex small location so just one area. good enough name? (if you change it then update your gntn example sideop) --xMin,yMin,xMax,yMax, in smallblock coords. see Tpp.CheckBlockArea loadArea={106,104,110,107}, activeArea={107,105,109,106}, invokeArea={107,105,109,106}, }, },--questAreas ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addons/ReadMe.md` Infinite Heaven provides a framework for many addons (mods that add to base game or IH features) This folder (\mod\docs\Infinite Heaven\Addons) has documents and examples that describe them. Generally an addon will add an info lua script in a specific folder in the \mod folder. In documentation and comments these are usually either refered to as addon file or Info. Subfolders of this folder with addon example are named after the specific addon folders, otherwise they will be noted in the document for the type of addon. TODO: bodyInfo fovaInfo (kinda paired) locations missions quests weaponIdTables TODO: Not classic addons, but can be saved loaded via ih features: avatars chimeras emblems ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addons/locations/AFC0.lua` ```lua --AFC0.lua --location addon locationInfo reference of all parameters rather than a sane example --see InfMission for implementation of system and other notes --most of the ALL_CAPS, or other code looking reference in the comment after an entry below is a reference to the base game data structure the option is adding to. --you can look up those references in the base game lua (Infinite Heaven github repo has all of them with further notes) to get more idea what they do local this={ description="Jade Forest",--just for IH --unique identifier for location, in theory this doesnt have to be just 4 characters (VERIFY) locationName="AFC0", --currently should be unique across all community locations, in the future this may be ignored if I can build a system that uses the locationName instead --see (and add to) https://mgsvmoddingwiki.github.io/Custom_Missions_List/ to avoid conflicts locationId=101, packs={"/Assets/mgo/pack/location/afc0/afc0.fpk"},-- TppMissionList.locationPackTable entry RENAMED was: packs locationMapParams={-- \Assets\tpp\pack\mbdvc\mb_dvc_top.fpkd \ mbdvc_map_location_parameter.lua entry stageSize=556*1, scrollMaxLeftUpPosition=Vector3(-255,0,-275), scrollMaxRightDownPosition=Vector3(255,0,275), highZoomScale=2, middleZoomScale=1, lowZoomScale=.5, locationNameLangId="mgo_idt_Jungle", stageRotate=0, heightMapTexturePath="/Assets/mgo/ui/texture/map/afc0/afc0_iDroid_clp.ftex", photoRealMapTexturePath="/Assets/mgo/ui/texture/map/afc0/afc0_jungle_sat_clp.ftex" }, globalLocationMapParams={ -- see \Assets\tpp\pack\mbdvc\mb_dvc_top.fpkd \ mbdvc_map_location_parameter.lua GetGlobalLocationParameter sectionFuncRankForDustBox = 4, sectionFuncRankForToilet = 4, sectionFuncRankForCrack = 6, isSpySearchEnable = true, isHerbSearchEnable = true,--tex IH will automatically patch in support for disableHerbSearch spySearchRadiusMeter = { 40.0, 40.0, 35.0, 30.0, 25.0, 20.0, 15.0, 10.0, }, spySearchIntervalSec = { 420.0, 420.0, 360.0, 300.0, 240.0, 180.0, 120.0, 60.0, }, herbSearchRadiusMeter = { 0.0, 0.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, }, }, questAreas={--tex defines quest areas for location, see TppQuestList.questList . { areaName="tent", --xMin,yMin,xMax,yMax, in smallblock coords. see Tpp.CheckBlockArea. debug menu ShowPosition will log GetCurrentStageSmallBlockIndex, or you can use whatever block visualisation in unity you have loadArea={116,134,131,152},--load is the larger area, so -1 minx, -1miny, +1maxx,+1maxy vs active activeArea={117,135,130,151}, invokeArea={117,135,130,151},--same size as active, but keeping here to stay same implementation as vanilla }, }, requestTppBuddy2BlockController=true,--tex not sure, see TppLocation.SetBuddyBlock and its caller TppMissionList.GetLocationPackagePath weatherProbabilities={-- see TppWeather.weatherProbabilitiesTable {TppDefine.WEATHER.SUNNY,80}, {TppDefine.WEATHER.CLOUDY,20} }, extraWeatherProbabilities={-- see TppWeather.extraWeatherProbabilitiesTable {TppDefine.WEATHER.RAINY,50}, {TppDefine.WEATHER.FOGGY,50} }, heliSpace=40020,--heliSpace for location, will be overidden by any missionInfo heliSpace setting (see missionInfo heliSpace for more notes) }--this return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addons/missions/helispace_addon_test.lua` ```lua --helispace_addon_test.lua --test helisspace addon using vanilla game location (afgh) as idle space and mission prep space local this={ missionCode=43000, location="AFGH", } --tex currently not much different from TppMissionList.missionPackTable[40010] (or any of the other helispace missionPackTables) --just pointing to the test mission area fpk (the only pack listed here that you should need to mod) this.missionPacks=function(missionCode) if gvars.ini_isTitleMode then TppPackList.SetDefaultMissionPackLabelName() end TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/heli/heli_common_script.fpk" if TppPackList.IsMissionPackLabel"PS3Store"then TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/common/title_sequence.fpk" TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/common/helispace_ps3store.fpk" elseif TppPackList.IsMissionPackLabel"avatarEdit"then TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/common/title_sequence.fpk" TppPackList.AddAvatarEditPack() TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/heli/h40010/h40010_avatar.fpk" else TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/common/title_sequence.fpk" TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/heli/heli_ui.fpk" TppPackList.AddMissionPack(TppDefine.MISSION_COMMON_PACK.HELICOPTER) --TppPackList.AddLocationCommonScriptPack(missionCode)--tex has not been updated to be addon aware, so we'll manually list ours below TppPackList.AddMissionPack"/Assets/tpp/pack/location/afgh/pack_common/afgh_script.fpk" --TppPackList.AddDefaultMissionAreaPack(missionCode)--tex is based on mission code, so we'll manually list ours below instead --tex for test is pretty much a copy of h40010_area.fpk/d, --with bare minimum of _sequence.fox2 player locator shifted and flor_common_asset.fox2 StageCenter and PlayerPosition shifted --to test changes between idle helispace and prep space. --in order for player to not go 'missing' at title, instead of shifting player_locator_0000 create a new generic Locator and point HelispaceLocatorIdentifier PlayerLocator to that instead TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/heli/hbaseloc/hbaseloc_area.fpk" end TppHostage2.SetHostageType{gameObjectType="TppHostage2",hostageType="Mob"}--tex for vanilla acc pilot end this.heliSpace={ --want to be helispace for all locations, --if you use this then technically you dont need any other locations or missions entries --however other helispaces that do specify missions,locations will be selected first ALL=true, --missions this is helispace for -- missions={ -- 30050, -- }, -- --locations this is helispace for -- locations={ -- "MAFR", -- }, }--heliSpace --tex if your _sequence.fox2 PlayerLocator for helispace idle is on actual location terrain, this will load it in rather than leave it with no LOD this.setStageBlockOnHeliSpace=true --RequestToPlayDirectMotion table (see InfMotion for notes) for helispace idle, replaces sitting in heli anim this.inHeliSpaceDirectMotion={ "/Assets/mgo/motion/SI_game/fani/bodies/snam/snamidl/snamidl_s_20_st.gani", --NOTE: there's probably lots of inventive ways to have a player break the game by letting them roam around in existing location while in helispace. You should probably have a very locked down/corraled custom area if you're going to do this false,--holdMotion: true = freeze anim at end, false = return to control of player in helispace idle "",--gameObjectTargetName "",--cnpTargetName "",--mtpTargetName true,--repeatMotion: holdMotion prevents controls/idroid from working, unless this is set to true } --heli_common_sequence does a bunch of WeatherManager.RequestTag calls as different points, --mostly its just helispace idle and prep space --but customize weapon and customize heli/vehicle get there own tags for whatever reason --also in vanilla helispace area flor_common_asset has a trap that requesttags sortie_space (but if you're bypassing that then it wont be hit) --vanilla values: -- this.heliSpaceWeatherTags={ -- title="heli_space",--tex the vanilla game doesn't set this differently, but IH kind of seperates title from helispace idle -- heliSpace="heli_space", -- prepSpace="sortie_space", -- customizeVehicle="sortie_space_heli",--Customize_Target_Helicopter, Customize_Target_Vehicle -- customizeWeapon="sortie_space_ShadowShort",--Customize_Target_Weapon -- } this.weatherTags={ title="heli_space", --tex the helispace test places all spaces (except title) on afgh world terrain heliSpace="default",--tex normal world weather tag prepSpace="default", customizeVehicle="default",--Customize_Target_Helicopter, Customize_Target_Vehicle customizeWeapon="default",--Customize_Target_Weapon } --tex Used by a few camera setup functions in heli_common_sequence --in the base game (a simplified version of) the table is only used for UpdateCameraParameter --if this table does not exist then it will use the vanilla (see InfHeliSpace.SelectCameraParameter) --i've expanded it for more heli_common_sequence camera functions ----Defaults: the parameters that follow after it will merge over those values. --linkKey: EntityLink key in PreparationStageIdentifier (flor_common_asset.fox2 in vanilla heliSpaces), will set aroundCam.target with locators position --aroundCam table: is a SetAroundCameraManualModeParams table (search the game lua for other parameters it supports) --aroundCam target=Vector3(x,y,z) will override linkKeys locator position --aroundCam ignoreObjectType: GameObject name (ex: "TppWalkerGear2") will be used by function to set aroundCam.ignoreCollisionGameObjectId --the SetCameraStageCenter* functions are a bit messy since they have a seperate setting if buddy horse is on the stage this.SelectCameraParameter={--tex heli_common_sequence defaults --for SetCameraStageCenter function > --handles the default camera for mission prep, when not in any of the sub menus of mission prep SetCameraStageCenter={--Defaults linkKey="StageCenter", --tex dumb values so you notice the difference --aroundCam={distance=400000.0,targetInterpTime=0.3,ignoreCollisionGameObjectName="Player"}, --normal values: aroundCam={distance=4.0,targetInterpTime=0.3,ignoreCollisionGameObjectName="Player"}, rotation={rotX=-5,rotY=170,interpTime=0.3} }, SetCameraStageCenter_Horse={ linkKey="StageCenter_Horse", aroundCam={distance=4.5}, }, --for SetCameraStageCenter function< --for SetCameraStageCenter_Go function> --called on SortieTimeSelect SetCameraStageCenter_Go={--Defaults linkKey="StageCenter", --tex dumb values so you notice the difference aroundCam={distance=3000,targetInterpTime=0.6,ignoreCollisionGameObjectName="Player"}, --normal values: aroundCam={distance=3.0,targetInterpTime=0.6,ignoreCollisionGameObjectName="Player"}, rotation={rotX=-5,rotY=70,interpTime=0.6} --normal values: rotation={rotX=-5,rotY=170,interpTime=0.6} }, SetCameraStageCenter_Go_Horse={ linkKey="StageCenter_Horse", aroundCam={distance=3.0}, }, --for SetCameraStageCenter_Go function< --for SetCameraStageCenter_GoOut function> --called OnMissionPreparetionEnd --the values in vanilla are actually just the same as SetCameraStageCenter SetCameraStageCenter_GoOut={--Defaults linkKey="StageCenter", --tex dumb values so you notice the difference aroundCam={distance=400,targetInterpTime=0.3,ignoreCollisionGameObjectName="Player"}, --normal values: aroundCam={distance=4.0,targetInterpTime=0.3,ignoreCollisionGameObjectName="Player"}, rotation={rotX=-5,rotY=17,interpTime=0.3} --normal values: rotation={rotX=-5,rotY=170,interpTime=0.3} }, SetCameraStageCenter_GoOut_Horse={ linkKey="StageCenter_Horse", aroundCam={distance=4.5}, }, --for SetCameraStageCenter_GoOut function< --for UpdateCameraParameter> --handles most of the camera settings for sub menus of mission prep, but also the customize menu outside of mission prep UpdateCameraParameter={--Defaults aroundCam={focusDistance=8.175,targetInterpTime=0.3}}, MissionPrep_FocusTarget_Weapon={ linkKey="WeaponPosition", --tex dumb values so you notice the difference aroundCam={distance=100000.5,focusDistance=1.5,aperture=1.6,targetIsPlayer=false}, --normal values: aroundCam={distance=1.5,focusDistance=1.5,aperture=1.6,targetIsPlayer=false}, rotation={rotX=-10,rotY=17,interpTime=0.3}}, --normal values: rotation={rotX=-10,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_PrimaryWeapon={linkKey="PlayerPosition",aroundCam={distance=3.0},rotation={rotX=5,rotY=230,interpTime=0.4}}, MissionPrep_FocusTarget_PrimaryWeapon_HIP={linkKey="PlayerPosition",aroundCam={distance=2.5},rotation={rotX=5,rotY=220,interpTime=0.4}}, MissionPrep_FocusTarget_PrimaryWeapon_BACK={linkKey="PlayerPosition_Up",aroundCam={distance=3.0},rotation={rotX=5,rotY=320,interpTime=0.4}}, MissionPrep_FocusTarget_SecondaryWeapon={linkKey="PlayerPosition",aroundCam={distance=3.0},rotation={rotX=-5,rotY=160,interpTime=0.3}}, MissionPrep_FocusTarget_SecondaryWeapon_WEAPON={linkKey="PlayerPosition",aroundCam={distance=2.0},rotation={rotX=-5,rotY=150,interpTime=0.3}}, MissionPrep_FocusTarget_SecondaryWeapon_ARM={linkKey="PlayerPosition_Up",aroundCam={distance=2.0},rotation={rotX=-5,rotY=230,interpTime=0.3}}, MissionPrep_FocusTarget_SupportWeapon={linkKey="PlayerPosition_Up",aroundCam={distance=1.8},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_Item={linkKey="PlayerPosition_Up",aroundCam={distance=3.3},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_Suit={linkKey="PlayerPosition_Up",aroundCam={distance=3.3},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_HeadOption={linkKey="PlayerHeadPosition",aroundCam={distance=2.3},rotation={rotX=-5,rotY=170,interpTime=0.3}}, --tex dumb values so you notice the difference MissionPrep_FocusTarget_Player={linkKey="PlayerPosition_Up",aroundCam={distance=300.3},rotation={rotX=-50,rotY=100,interpTime=0.3}}, --normal values: MissionPrep_FocusTarget_Player={linkKey="PlayerPosition_Up",aroundCam={distance=3.3},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_BuddyQuiet={linkKey="BuddyQuietPosition",aroundCam={distance=3.6,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_BuddyQuietEquip={linkKey="BuddyQuietPosition_Up",aroundCam={distance=3.2,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=270,interpTime=0.4}}, MissionPrep_FocusTarget_BuddyDog={linkKey="BuddyDogPosition",aroundCam={distance=3.8,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=182,interpTime=0.3}}, MissionPrep_FocusTarget_BuddyHorse={linkKey="BuddyHorsePosition_2",aroundCam={distance=5.3,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=180,interpTime=0.3}}, MissionPrep_FocusTarget_BuddyWalker={linkKey="BuddyPosition",aroundCam={distance=4.0,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_BuddyBattleGear={linkKey="BuddyPosition",aroundCam={distance=5.8,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=170,interpTime=0.3}}, MissionPrep_FocusTarget_Vehicle={linkKey="VehicleCameraPosition",aroundCam={distance=9.0,ignoreObjectType="TppVehicle2"},rotation={rotX=24,rotY=65,interpTime=0.6}}, MissionPrep_FocusTarget_None={linkKey="StageCenter",aroundCam={distance=3.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Weapon={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.7},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Buddy={linkKey="CustomizeBuddyPosition",aroundCam={distance=4.5,ignoreObjectType="TppWalkerGear2"},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Weapon_Handgun={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.0},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_SubMachinegun={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.4},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_AssaultRifle={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.5},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_ShotGun={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.5},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_GrenadeLauncher={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.5},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_SniperRifle={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.7},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_MachineGun={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.7},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Weapon_Missile={linkKey="CustomizeWeaponPosition",aroundCam={distance=1.7},rotation={rotX=0,rotY=180,interpTime=0.3}}, Customize_Target_BuddyDog={linkKey="CustomizeBuddyPosition",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Dog_Body={linkKey="CustomizeBuddyPosition",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Dog_Eye={linkKey="CustomizeBuddyPosition",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_BuddyHorse={linkKey="CustomizeBuddyPosition_HorsePos",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Horse_Body={linkKey="CustomizeBuddyPosition_HorsePos",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Horse_Head={linkKey="CustomizeBuddyPosition_HorsePos",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Horse_Leg={linkKey="CustomizeBuddyPosition_HorsePos",aroundCam={distance=4.5},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_BuddyWalker={linkKey="CustomizeBuddyPosition_DWPos",aroundCam={distance=4.8},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_DW_Body={linkKey="CustomizeBuddyPosition_DWPos",aroundCam={distance=4.8},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_DW_Manipulator={linkKey="CustomizeBuddyPosition_DWPos",aroundCam={distance=4.0},rotation={rotX=-5,rotY=120,interpTime=0.3}}, Customize_Target_DW_MainWeapon={linkKey="CustomizeBuddyPosition_Head",aroundCam={distance=3.0},rotation={rotX=7,rotY=185,interpTime=0.3}}, Customize_Target_DW_SubWeapon={linkKey="CustomizeBuddyPosition_Head",aroundCam={distance=3.0},rotation={rotX=3,rotY=115,interpTime=0.3}}, Customize_Target_DW_Head={linkKey="CustomizeBuddyPosition_Head",aroundCam={distance=3.0},rotation={rotX=25,rotY=90,interpTime=0.3}}, Customize_Target_DW_Color={linkKey="CustomizeBuddyPosition_DWPos",aroundCam={distance=4.8},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Heli_Body={linkKey="CustomizeHelicopterCameraPosition",aroundCam={distance=16},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Heli_Main={linkKey="CustomizeHelicopterCameraPosition_MainWeapon",aroundCam={distance=10},rotation={rotX=25,rotY=150,interpTime=0.3}}, Customize_Target_Heli_OpFlare={linkKey="CustomizeHelicopterCameraPosition",aroundCam={distance=16},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Heli_OpArmor={linkKey="CustomizeHelicopterCameraPosition",aroundCam={distance=16},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Heli_Color={linkKey="CustomizeHelicopterCameraPosition",aroundCam={distance=16},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Helicopter={linkKey="CustomizeHelicopterCameraPosition",aroundCam={distance=16},rotation={rotX=-5,rotY=170,interpTime=0.3}}, Customize_Target_Vehicle={linkKey="CustomizeVehicleCameraPosition",aroundCam={distance=12,ignoreObjectType="TppVehicle2"},rotation={rotX=15,rotY=150,interpTime=0.2}}, }--SelectCameraParameter --HeliSpace only missionInfo parameters< return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addons/missions/tex_acf0_jade_forest.lua` ```lua --tex_acf0_jade_forest.lua --mission addon missionInfo reference of all parameters rather than a sane example --see InfMission for implementation of system and other notes --most of the ALL_CAPS, or other code looking reference in the comment after an entry below is a reference to the base game data structure the option is adding to. --you can look up those references in the base game lua (Infinite Heaven github repo has all of them with further notes) to get more idea what they do local this={ --unique identifier for the mission, can be anything, just make it unique enough to not conflict with any other community missions --none of the different parts have any meaning to the system, it just uses the whole string as an identifier --community missions made before now (r262) will not have this, they will fall back to using the file name as missionName missionName="tex_acf0_jade_forest", description="Jade Forest",-- Description for IH menu. --currently should be unique across all community missions, in the future this may be ignored if I can build a system that uses the missionName instead --see (and add to) https://mgsvmoddingwiki.github.io/Custom_Missions_List/ to avoid conflicts missionCode=12020, --locationName of location this mission uses, see TppDefine.LOCATION_ID for base game locationNames location="AFC0", --doesn't add mission to idroid/internal mission list (so no mission completion/ranking saved). --Still loadable via Load addon mission, or TppMission.ReserveMissionClear, or Transistion system hideMission=false, --TppMissionList.missionPackTable entry, can be table of fpk names or function of packlist adding calls. RENAMED was: packs missionPacks=function(missionCode) TppPackList.AddMissionPack(TppDefine.MISSION_COMMON_PACK.DD_SOLDIER_WAIT) TppPackList.AddMissionPack"/Assets/tpp/pack/mission2/story/s13000/s13000_area.fpk" end, fovaSetupFunc=function(locationName,missionId)-- TppEneFova.fovaSetupFuncs entry local faces={} table.insert(faces,{TppEnemyFaceId.dds_balaclava2,10,10,0}) table.insert(faces,{TppEnemyFaceId.dds_balaclava6,2,2,0}) table.insert(faces,{TppEnemyFaceId.dds_balaclava7,2,2,0}) TppSoldierFace.OverwriteMissionFovaData{face=faces} end, enableOOB=true,-- Enable the mission out of bound system startPos={-11.788,8.483,165.559},--TppDefine.NO_HELICOPTER_MISSION_START_POSITION entry - player spawn pos for non heli ride mission starts missionGuaranteeGMP=120000, --TppResult.MISSION_GUARANTEE_GMP - base gmp for mission on mission clear noAddVolunteers=false,--dont add any volunteer staff on mission complete missionTaskList={0,2,3,4,5,6},--see TppResult.MISSION_TASK_LIST - _sequence.missionObjectiveDefine taskNo to idroid mission task UI index, also used to numerate the valid tasks noArmorForMission=true,--TppEneFova.noArmorForMission - disallow heavy armor in the mission missionArmorType={TppDefine.AFR_ARMOR.TYPE_RC},--TppEneFova.missionArmorType - Armor type for pfs in mafr missionHostageInfos={count=1,lang=RENlang2,overlap=true},--TppEneFova.missionHostageInfos - for the mission hostage generation system, --tex mission start options, you can start off developing a mission addon with just providing a startPos (see above)> --but if you want to support some of the different mission start features the base game missions have these are the options --terms-wise the game kind of assumes that most missions will be started by chosing a heli route from idroid --you can see some setup there in missionMapParams heliLandPoint --otherwise its considered a NO_HELICOPTER_MISSION_START, mission addons use this mode by default if you only provide the startPos and no other options below clearExistingMissionStartSettings=true,--only for use when overriding a vanilla mission, clears all the mission start data so the following options you set can work cleanly clearMissionStartHeliRoute=true,--this will clear any heli route chosen for mission start, to clear way for startPos to work --TppDefine.NO_HELICOPTER_ROUTE_MISSION_LIST --An orderBox is the mission orders box that appears when chosing a mission from the idroid, --while in the free roam of the same location --for a mission without order box (without this and without orderBoxList it will be considered a NO_HELICOPTER_MISSION)> --in the base game all isNoHeliStartMission --(or more acuratly all missions that have entries in NO_HELICOPTER_ROUTE_MISSION_LIST also have entries in NO_ORDER_BOX_MISSION_LIST) isNoOrderBoxMission=true,--mission will use startPos as start position --TppDefine.NO_ORDER_BOX_MISSION_LIST,NO_ORDER_BOX_MISSION_ENUM heliRouteForNoBoxMission="rt_drp_mbqf_N",--tex will use heli with this route instead of starting on ground at startPos --TppDefine.NO_ORDER_FIX_HELICOPTER_ROUTE orderBoxList = { -- _sequence.missionStartPosition.orderBoxList -- TODO description "box_s13000_00", "box_s13000_01", }, --_orderBoxList.lua TODO description orderBoxBlockList = { "/Assets/tpp/pack/mission2/story/s13000/s13000_order_box.fpk" }, missionMapParams={--mbdvc_map_mission_parameter.missionParameters missionArea2 = { { name="trig_innerZone", vertices={ Vector3(-1130.04,180.00,859.60),Vector3(-748.37,180.00,1241.27),Vector3(-475.96,180.00,968.86),Vector3(-225.87,180.00,1218.95),Vector3(152.35,180.00,840.77),Vector3(152.35,180.00,-89.97),Vector3(-479.32,180.00,-89.97),Vector3(-857.62,180.00,288.33),Vector3(-857.62,180.00,587.19), }, }, }, safetyArea2 = { { name="trig_hotZone", vertices={ Vector3(-652.23,180.00,1012.55),Vector3(-536.76,180.00,897.08),Vector3(-463.86,180.00,969.99),Vector3(-193.42,180.00,699.55),Vector3(-193.40,180.00,39.43),Vector3(-539.49,180.00,39.43),Vector3(-845.91,180.00,345.86),Vector3(-845.91,180.00,587.93),Vector3(-961.38,180.00,703.40), }, }, }, -- Order box points when mission is highlighted in free mode missionStartPoint = {Vector3(-218.07,328.03,395.86),Vector3(-381.54,294.27,910.16),}, --GOTCHA: heli routeIds need to be unique across all locations heliLandPoint = { {point=Vector3(-351.61,321.89,768.34),startPoint=Vector3(-91.82,331.89,918.56),routeId="lz_drp_enemyBase_S0000|rt_drp_enemyBase_S_0000"}, {point=Vector3(-289.80,346.69,269.68),startPoint=Vector3(161.28,335.69,140.48),routeId="lz_drp_enemyBase_N0000|rt_drp_enemyBase_N_0000"}, {point=Vector3(-596.89,353.02,497.40),startPoint=Vector3(-946.28,309.02,981.35),routeId="lz_drp_enemyBase_I0000|rt_drp_enemyBase_I_0000"}, }, },--missionMapParams weaponIdTable={-- alternatively a string of the TppEnemy.weaponIdTable ex weaponIdTable="SOVIET_A", IMPLEMENTATION: GetWeaponIdTable, see also InfWeaponIdTable for IH global weaponIdTable override PF_A={ NORMAL={ HANDGUN=TppEquip.EQP_WP_East_hg_010, SMG=TppEquip.EQP_WP_East_sm_010, ASSAULT=TppEquip.EQP_WP_East_ar_010, SNIPER=TppEquip.EQP_WP_East_sr_011, SHOTGUN=TppEquip.EQP_WP_Com_sg_011, MG=TppEquip.EQP_WP_East_mg_010, MISSILE=TppEquip.EQP_WP_East_ms_010, SHIELD=TppEquip.EQP_SLD_SV }, }, },--weaponIdTable heliSpaceFlags={-- Sortie/mission prep screen feature flags SkipMissionPreparetion=false, -- No sortie prep, like vanilla Mother Base. NoBuddyMenuFromMissionPreparetion=true, -- No buddy select in the sortie NoVehicleMenuFromMissionPreparetion=true, -- No vehicle select in the sortie DisableSelectSortieTimeFromMissionPreparetion=true, -- Only ASAP as deployment time option }, defaultDropRoute="lz_drp_bridge_S0000|rt_drp_bridge_S_0000",--tex story missions only (not free roam), --TppDefine.DEFAULT_DROP_ROUTE not exactly sure how/what its used for (disapears into exe via TppUiCommand.RegisterDefaultLandPoint) --tex DEBUGNOW debating whether to have this here or in locationInfo to save duplication, --TppLandingZoneData is defined in mission pack fox2 so author would be duplicating anyhoo --but using this to build TppLandingZone MissionLandingZoneTable which is pretty much all the lzs dumped in for managing disabling the lzs in idroid --tex see also comment above InfLZ.lzInfo for the breakdown of what the data is from --rlc v isAssault to mark it red in the mission list in the ACC --gimmickInfo for the anti-air radar gimmick that if not broken, will disable the lz --rlc ^ lzInfo={ ["lz_bridge_S0000|lz_bridge_S_0000"]={ approachRoute="lz_bridge_S0000|rt_apr_bridge_S_0000", dropRoute="lz_drp_bridge_S0000|rt_drp_bridge_S_0000", returnRoute="lz_bridge_S0000|rt_rtn_bridge_S_0000", }, ["lz_citadelSouth_S0000|lz_citadelSouth_S_0000"]={ approachRoute="lz_citadelSouth_S0000|rt_apr_citadelSouth_S_0000", dropRoute="lz_drp_citadelSouth_S0000|rt_drp_citadelSouth_S_0000", returnRoute="lz_citadelSouth_S0000|rt_rtn_citadelSouth_S_0000", isAssault=true, --rlc v addition gimmickInfo={ name="cliffTown_aacr001", type=TppGameObject.GAME_OBJECT_TYPE_IMPORTANT_BREAKABLE, locatorName="afgh_antn006_gim_n0000|srt_afgh_antn006", dataSetName="/Assets/tpp/level/location/afgh/block_large/cliffTown/afgh_cliffTown_gimmick.fox2" },--rlc ^ }, },--lzInfo enableQuests={"lab_q39011","lab_q80700","lab_q10700"},--Enables quests in story missions. if Ivar enableMissionQuest. handled by rlcs InfMissionQuest heliSpace=40020,--heliSpace for the mission, will load when landing spot is selected to go to mission prep (if ivar heliSpace_loadOnSelectLandPoint is on), and on returning from mission --Only for HeliSpace addons> --As a function, a replacement function for heli_common_sequence.TitleModeOnEnterFunction --As a table, RequestToPlayCameraNonAnimation parameters for the title screen. --See InfHeliSpace.TitleModeOnEnterFunction TitleModeOnEnter={ focalLength = 5.7, aperture = 1.05, focusDistance = 0.9, positionAndTargetMode = true, position = Vector3{ 5.213, 1180.166, 5.106}, target = Vector3{ -0.222, 1198.16, -0.35}, }, --override for heli_common_sequence.UpdateCameraParameter --only use this if you want full --alternatively use specific entries in SelectCameraParameter UpdateCameraParameter=function(focusTarget,immediately) end,-- --camera settings for mission prep and weapon customization --can be table that will override just the default parameters (see InfHeliSpace SelectCameraParameter) (more specifically its merged, so if theres a default parameter you want to clear set it to nil) SelectCameraParameter={ --for SetCameraStageCenter SetCameraStageCenter={ linkKey="StageCenter", aroundCam={distance=4.0,targetInterpTime=0.3,ignoreCollisionGameObjectName="Player"}, rotation={rotX=-5,rotY=170,interpTime=0.3} }, --for UpdateCameraParameter MissionPrep_FocusTarget_SecondaryWeapon={ linkKey="PlayerPosition", aroundCam={distance=3.0}, rotation={rotX=-5,rotY=160,interpTime=0.3}}, }, --Only for HeliSpace addons< }--this return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Addons/weaponIdTables/example.lua` ```lua --example.lua --example weaponIdTable addon --should install addon to mod\weaponIdTables\ --selected in game by weaponTableGlobal - Global soldier weapon table in Free/Mission/MB --via Enemy Prep menu > Custom soldier equip menu --REF see TppEnemy.weaponIdTable for default table local this={ description="Example weaponIdTable", } this.weaponIdTable={ SOVIET_A={ NORMAL={ HANDGUN=TppEquip.EQP_WP_West_hg_030,--geist p3 machine pistol grade 4 - shows shotgun icon but clearly isnt, SMG=TppEquip.EQP_WP_West_sm_01b, ASSAULT=TppEquip.EQP_WP_West_ar_05b, SNIPER=TppEquip.EQP_WP_EX_sr_000,--molotok-68 grade 9 SHOTGUN=TppEquip.EQP_WP_Com_sg_018, MG=TppEquip.EQP_WP_West_mg_03b,--alm48 flashlight grade 4 --TppEquip.EQP_WP_West_mg_037, MISSILE=TppEquip.EQP_WP_Com_ms_02b, SHIELD=TppEquip.EQP_SLD_DD_01, }, },--SOVIET_A PF_A={ NORMAL={ HANDGUN=TppEquip.EQP_WP_West_hg_030,--geist p3 machine pistol grade 4 - shows shotgun icon but clearly isnt, SMG=TppEquip.EQP_WP_West_sm_01b, ASSAULT=TppEquip.EQP_WP_West_ar_05b, SNIPER=TppEquip.EQP_WP_EX_sr_000,--molotok-68 grade 9 SHOTGUN=TppEquip.EQP_WP_Com_sg_018, MG=TppEquip.EQP_WP_West_mg_03b,--alm48 flashlight grade 4 --TppEquip.EQP_WP_West_mg_037, MISSILE=TppEquip.EQP_WP_Com_ms_02b, SHIELD=TppEquip.EQP_SLD_DD_01, }, },--PF_A PF_B={ NORMAL={ HANDGUN=TppEquip.EQP_WP_West_hg_030,--geist p3 machine pistol grade 4 - shows shotgun icon but clearly isnt, SMG=TppEquip.EQP_WP_West_sm_01b, ASSAULT=TppEquip.EQP_WP_West_ar_05b, SNIPER=TppEquip.EQP_WP_EX_sr_000,--molotok-68 grade 9 SHOTGUN=TppEquip.EQP_WP_Com_sg_018, MG=TppEquip.EQP_WP_West_mg_03b,--alm48 flashlight grade 4 --TppEquip.EQP_WP_West_mg_037, MISSILE=TppEquip.EQP_WP_Com_ms_02b, SHIELD=TppEquip.EQP_SLD_DD_01, }, },--PF_B PF_C={ NORMAL={ HANDGUN=TppEquip.EQP_WP_West_hg_030,--geist p3 machine pistol grade 4 - shows shotgun icon but clearly isnt, SMG=TppEquip.EQP_WP_West_sm_01b, ASSAULT=TppEquip.EQP_WP_West_ar_05b, SNIPER=TppEquip.EQP_WP_EX_sr_000,--molotok-68 grade 9 SHOTGUN=TppEquip.EQP_WP_Com_sg_018, MG=TppEquip.EQP_WP_West_mg_03b,--alm48 flashlight grade 4 --TppEquip.EQP_WP_West_mg_037, MISSILE=TppEquip.EQP_WP_Com_ms_02b, SHIELD=TppEquip.EQP_SLD_DD_01, }, },--PF_C SKULL_CYPR={ NORMAL={ HANDGUN=TppEquip.EQP_WP_West_hg_020, SMG=TppEquip.EQP_WP_East_sm_030 } },--SKULL_CYPR SKULL={ NORMAL={ HANDGUN=TppEquip.EQP_WP_West_hg_030,--geist p3 machine pistol grade 4 - shows shotgun icon but clearly isnt, SMG=TppEquip.EQP_WP_West_sm_01b, ASSAULT=TppEquip.EQP_WP_West_ar_05b, SNIPER=TppEquip.EQP_WP_EX_sr_000,--molotok-68 grade 9 SHOTGUN=TppEquip.EQP_WP_Com_sg_018, MG=TppEquip.EQP_WP_West_mg_03b,--alm48 flashlight grade 4 --TppEquip.EQP_WP_West_mg_037, MISSILE=TppEquip.EQP_WP_Com_ms_02b, SHIELD=TppEquip.EQP_SLD_DD_01, }, },--SKULL CHILD={ NORMAL={ HANDGUN=TppEquip.EQP_WP_East_hg_010, ASSAULT=TppEquip.EQP_WP_East_ar_020 } }--CHILD }--weaponIdTable return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Development.md` Interested in mgsv/IH modding? Moding wiki: https://metalgearmodding.fandom.com/wiki/Metal_Gear_Modding_Wiki Modding discord: https://discord.gg/3rVFWf2vbj mgs_tpp\mod\devmodules includes some ih modules for specific aspects of developing. Look at the comments at the top of each module for info about what its for. Currently you need to copy the modules you want to use into mgs_tpp\mod\modules ### `ih_kap:tpp/gamedir-ih/GameDir/mod/docs/Infinite Heaven/Readme.md` #### Infinite heaven r266 - 2026-08-07 by tin man tex, fork by caplag For MGSV version 1.15 (in title screen), 1.0.15.4 in exe, day3900 in version_info.txt Compatible IHHook version: r24 or later A mod for Metal Gear Solid V: The Phantom Pain intended to extend gameplay through customizable settings and features, as well as providing addon systems to support other mods. Addon systems include allowing custom sidops, missions, enemy soldier types. Has several hundred toggleable options ranging from Subsistence mode for all missions, replay side-ops, Mother base invasions with multiple attack helicopters, Skull attacks in Free roam, Free-cam, skip heli rides, customization of enemy and mother base gear, foot, heavy vehicle and heli patrols in free roam, and much more. It is highly recommended to use IHHook, which is a script extender and graphical menu that has mouse and keyboard support, alongside IH. [IHHook on NexusMods](https://www.nexusmods.com/metalgearsolidvtpp/mods/1226) All IH documents can be found in MGS_TPP\mod\docs\Infinite Heaven\ (once installed), or the [Infinite Heaven GitHub repo](https://github.com/kapuragu/InfiniteHeaven/tree/dev/tpp/gamedir-ih/GameDir/mod/docs/Infinite%20Heaven) See the youtube channel for demonstrations of many features: [ youtube.com/@TinManSquad ](https://www.youtube.com/@TinManSquad) For description of all see ['Features and Options.md'](https://github.com/kapuragu/InfiniteHeaven/tree/dev/tpp/gamedir-ih/GameDir/mod/docs/Infinite%20Heaven/Features%20and%20Options.md) ##### Recent changes/additions: For older updates see ['Change Log.md'](https://github.com/kapuragu/InfiniteHeaven/tree/dev/tpp/gamedir-ih/GameDir/mod/docs/Infinite%20Heaven/Change%20Log.md) ###### r266 - 2026-08-07 --------------------- IHHook r24 and game version 1.0.15.4/day3900 bump. maxGraphBounderCount added to Sahelan NavWorld for retali8. Fixed typo in Force enable Quarantine platform soldiers. Documentation & reformatting. Finally fixed sortie prep stage centers. Added Yazed0071's V_Framework's damage and equip parameter names. Fixed errors with side ops in missions in certain missions with multiple pack labels, like Hellbound. Added missionOpenTapes and missionClearTapes cassette tape name lists to mission add-ons. Rearranged side op repop requirements to hopefully match vanilla behavior better. Added debug menu option to allow verbose logging of unknown messages. Since new ones are being added, this will help performance. Added option to enable soldiers on the Animal Conservation Platform in the Mother Base menu. Potentially fixed crash with clearing customized weapon slots. Added options to MB characters menu that use Ocelot's unused railing animations. Added compatibility workaround for alicealys's tpp-mod. ###### r265 - 2026-05-22 --------------------- Fixed a vanilla bug that let players steal knocked-out staff during friendly FOB visits. Added hooks for custom result music, for real this time. Fixed Anti-Air Radar Repop, somehow, possibly. Fixed side ops in missions not being filtered when Force is off, which would lead to the Search for Quiet side op being loaded into Cloaked In Silence. Fixed revenge point decay on Mother Base visits being locked behind Mother Base morale increase on Mother Base visits. Added requested Disable Volunteers option in Progression Menu. ###### r264 - 2026-05-11 --------------------- Added hooks for custom result music, add-on mission typing text telop, override for heli force pullout on checkpoint save. Added IHExt back in due to demand. No functionality changed, just recompiled the same code. Fixed sortie prep camera angle when deploying to mission with D-Horse. Fixed a vanilla bug that let players steal knocked-out staff during friendly FOB visits. Actually fixed shift changes on FOBs. ###### r263b - 2026-04-26 --------------------- Fixed UAV battery drain and shift changes on FOBs. ###### r263a - 2026-04-24 --------------------- Zeta r20 compatibility issue from r262 fixed. ###### r263 - 2026-04-24 --------------------- Vehicle reinforcements fix and expansion: fixed an issue where vehicle reinforcements packs weren't being added to the rotation in vanilla game instances (A HERO'S WAY, CURSED LEGACY). This spun off into further fixes of vanilla oversights (setting up the reinforce block soldiers' CP, soldier type and sub-type, applying the vehicle camo by sub-type) and optional additions to the system (an optional reinforce block unload to allow for more reinforce waves in the same session, overriding reinforce vehicle color class) Fixed for the ACC add-on system: sortie camera center position, D-Dog center position. Fixed loading screen crash on non-lethal FOBs with sneaking suits equipped on enemy soldiers. Fixed freeze on module reload. Added add-on mission assault LZ support, with connection to destroyable anti-air radar gimmicks optional. Fixed the primary NONE weapon slot being replaced with an SVG-67 during FOB Free Placement mode. Fixed the Start Offline softlock on bootup. Fixed Bionic Arm upgrade ranks. Minimal settings will now entirely disable the upgrades. Fixed control lock remaining after checkpoints which saved when the Infinite Heaven menu was open are reloaded. Side Ops in Missions can now be enabled in PHANTOM LIMBS. Added Minute and Second setting options in the Time Scale menu. Added Weather Menu to Cutscene Menu. Added back old Quarantine Platform boss events as an option. When enabled, damaging Skulls and the body of the Man on Fire will spawn one of them. The Man on Fire will disappear immediately, however. He's afraid of water. Fixed free camera input still being executed on game pause. ###### r262 - 2023-06-19 --------------------- Fix: Hang on loading FOB. Not sure about this one, introduced the bug in 261, but checking 262 now bug not happening, so fixed it while reworking something else I guess. IHHook menu can be open when idroid is open, only mouse interaction though. titleMenu: Can open IHHook menu at title for and alternate way to abortToACC. Menu: Halts at top/bottom menus when holding down menu up/down. Repeat rate when using gui menu increased. Boss events: (Boss event menu in mission, Events menu > Boss event menu in ACC) The previous 'Skull attack' system has been reworked. Split into the underlying gameobject types of TppParasite2 (the Armor and Mist parasite skull units in the base game) TppBossQuiet2 (Camo/Sniper parasite Skull units and Quiet in the base game) TppVolgin2 (new for the ih system, Volgin/the man on fire) All boss types can have subTypes (like the base game ones mentioned above) which authors can develop addons for to give new appearances. (See MGS_TPP\mod\bosses\) Like the previous system an event is split into a countdown period randomized between the min and max time setting. Then chooses which boss types and subType for the boss type will be in the event. Then kicks off the actual attack/appearance of the boss(es) Multiple boss types can be included in an event (off by default, may be unstable with other ih features enabled) Only one sub type per boss type will be choosen. HeliSpace (ACC) mission addon support - thanks retali8 for building his own helispace and testing features. API: InfButton: Added some alternate/friendler mask names for some buttons READY_WEAPON for HOLD BINOCULARS for SUBJECT DIVE for EVADE Values are the same so doesn't matter which you use. Fix: GetGamePath not returning IHHook.GetGamePath. - thanks Environ for the related report and log files. Loading: TppMissionList.GetLocationPackagePath,GetMissionPackagePath will warn on missing fpks (according to snakebite.xml) Project (Infinite Heaven repo): Build settings: Extensions renamed to .buildmod Settings updated for mgsv_buildmod 1.1 Infinite Heaven repo\tpp\tpp.code-workspace: A vscode workspace for the dev (Infinite Heaven repo\tpp) folder. See Infinite Heaven repo\vscode.md for more info. tpp/vscode/vscode_hint-*: Generated by WriteVscodeHint. The vscode lua language server extension needs a bit of help making sense of mgsv/IHs setup, these lua files are parsed by the extension to map the files to their global modules (not used by IH mod itself). See Infinite Heaven repo\vscode.md for more info. Debug menu > Vscode menu: "Commands to generate files to support using vscode with a mgsv project." debug_WriteVscodeHintOnTitle - "Runs WriteVscodeHint when loading has reached Title" WriteVscodeHint - "Writes GameDir/mod/vscode/vscode_hint-mod.lua,vscode_hint-base_game.lua , writes a hint files for vscode lua language server extension of mod lua files loaded." debug_WriteVarsOnSave - "Runs WriteVars on IH save (which includes close IH menu)" WriteVars - "Writes vars, svars, gvars, mvars to GameDir/mod/vscode/, for manual perusal or use with vscode" debugMessages: Expanded from Off, On to: OFF, ALL, RECIEVED. ALL (same as pre r262 'On') logs all messages sent. RECIEVED only logs those that have modules subscribed to the message. PrintOnMessage log line now notes recievers of the message (if any). Errors in Message funcs should now directly log the point of the error, with the reciever/module noted in the PrintOnMessage log line after it. ###### r261 - 2023-03-24 --------------------- 'Warp to latest marker' now works when driving vehicle or walker gear. [youtube]FVc0bw9ooFs[/youtube] https://youtu.be/FVc0bw9ooFs Fix: disableHerbSearch possibly not working correctly. Fix: Randomize RouteSets being called despite individual options being off (though not if main setting was off). thanks caplag for a related report. Fix: "Custom soldier type in Free/Mission" resetting to off after game is next restarted. Was broken as of r247 - thanks Spectral for the report and troubleshooting files. Fix: enemy soldier type addons with multiple bodies not working, was broken as of r258 - thanks Spectral for the report. Fix: Some cases of Player Cam hook (FOV) not applying. Fix: InfSoldierFaceAndBody not retaining its data on script reload. Affected Apearance menu > Face option when Filter set to Head fova addons. Fix: Back in the dark ages I changed mvars.ene_soldierIDList itself from its original [cpId][soldierId]=cpDefine index to [cpId][soldierId]=soldierName as the value didnt seem to be used anywhere, and I wanted a lookup. Didn't really end up using it much, it was better to just iterate soldierDefine again, so its been reverted. (and API/dev warning I guess if anyone was using it as its modified form in their scripts) Fix: TppRevenge.SetEnableSoldierLocatorList (actually broken out by IH from _ApplyRevengeToCp) not applying in MB. Don't think it affected anything since it failed in a way that it was still applying revenge for all soliders in a cluster, just that it was applying multiple times (with exact same values). See the NOTE: in SetEnableSoldierLocatorList for my puzzlement about it. Fix: Custom DD female type no longer dependant on Custom DD (male) type being set. Either of them will default to normal DD mb body if setting is OFF. Motherbase menu > 'DD Suit', 'DD Suit female' renamed 'Custom DD type in MB' in line with 'Custom Soldier type in mission/free'. The underlying Ivars already were named customSoldierTypeMB_ALL, so you shouln't have to re-set the setting. AroundCam (FreeCam): When AroundCam on, but with Adjust-mode off, changing settings via will now update AroundCam. Fix: FreeCam hang if on when Abort mission. API: addon missionInfo (See notes in InfMission) clearExistingMissionStartSettings - only for use when overriding a vanilla mission, clears all the mission start data so the following options you set can work cleanly noBoxMissionHeliRoute / NO_ORDER_FIX_HELICOPTER_ROUTE support, requires isNoOrderBoxMission to be set clearMissionStartHeliRoute is to give actual support for NO_HELICOPTER_ROUTE_MISSION_LIST, before it was just riding on the assumption that if the addon author gave a startPos they wouldn't have heli routes/starts set up, this gives the same support as the base game for those missions. isNoOrderBoxMission, likewise giving explicit setting for NO_ORDER_BOX_MISSION - thanks caplag for the suggestions API: LoadExternalModule: Don't reload module if prevModule and not isReload. This was causing the early InfInit LoadExternalModuled modules Ivars,IvarsPersist and InfSoldierFaceAndBody to be reloaded. In the case of InfSoldierFaceAndBody it would loose all its data since those functions were called manually rather than PostAllModulesLoad. API: InfUserMarker.GetMarkerPosition return type changed from Vector3 to {x,y,z} as it's more commonly used through other IH functions. ###### r260 - 2023-03-10 --------------------- Fix: quest adding their missionPacks in helispace - thanks CapLag for an unrelated report that made me test something related. Fix: Repop of sideop 144 tent_q99040 "Secure the Remains of the Man on Fire" arival on quarantine platform leaving player stuck in helipad. Fix: Infinite load screen when IH startOnFoot used when force go to mb on quest clear > demo play. Another case of the prior fix in r259 for start on foot, which Repop of sideop 144 is also a case of. Fix: Several bugs setting quest flags incorrectly. Could result in some of the hidden/managed quests activating or not. Could also cause the all sideops disabled bug. A manual fix is to run "Reroll sideops selection" with "Repop mode" set to Allways. Ancillary ih saves (addon mission and quest states) are now cleared on new game. InfQuest / IH Sideops menu: [youtube]2UUXDfMfrso[/youtube] https://youtu.be/2UUXDfMfrso Renamed unlockSideOpNumber to quest_forceQuestNumber, just an internal name change to be more consistant and accurate. Though you will have to set the setting again. Renamed ihSideopsPercentageCount to quest_addonsCountForCompletion, just an internal name change to be more consistant and accurate. Though you will have to set the setting again. quest_updateRepopMode="Repop mode": "On none left"|"Allways" Lets you choose the behaviour of how repeatable sideops are refreshed. The update is run for the sideop area of a quest you just finished, or for all areas when changing many of the IH sideops options or rerolling sideops. The default behaviour 'On none left' will only repopulate sideops when there are no other uncompleted sideops, and all other repeatable sideops have been completed. 'Allways' will refresh repeatable quests every time the update is called. quest_setIsOnceToRepop="Repop one-time sideops" "Lets you force story and one-time sideops to be replayable." Replaces "Unlock Sideops mode". quest_selectForArea="Selection for Area mode" Renamed from sideOpsSelectionMode Categories removed, use the Sideops category filter menu instead. 'Random Addon' setting added to prioritize Addon sideops. Sideops are broken into areas to stop overlap, this setting lets you control the choice which repop sideop will be selected to be Active for the area. 'Random Addon' will prioritize Addon sideops first. All selection is still prioritized by uncompleted story sideops, then other uncompleted sideops, then repop sideops selected by this option. "Show on UI menu" Replaces "Show all open sideops" Settings for what sideops to show and how they should be sorted depedending on various parameters for sideops on the idroid sideops list. The vanilla behavior just shows current Active and Cleared sideops, in index order, which lets you see past progression/completion, Though since uncleared sideops do have priority, one will be selected for Active. So if there's multiple uncleared for an area they will not be shown, which gives you less of an idea of future progression. These option give you individual control for showing sideops depending on their conditions. For a given sideop multiple of the underlying conditions may be true at one time and either depend on your progress through the game, or from other IH settings. There is however a limit of 192 entries for the sideop list (there's 157 sideops in the base game), which some settings might push over if you have addon sideops, in which case some Cleared entries be randomly dropped from the list. See the notes for each option for more info. Sorting: Sorting will proceed through all flags that have a sorting setting (not set to None). So sort is in respect to the option above it. None: Will not apply any specific sort, so will just be in index order, but may be moved around if other flags sort it. The other settings, Top, Bottom or Ascending, Descending depending on the flag type, will sort as the settings suggest, but in relation to the prior flags. Since the final sort is by sideop index (the number on the left of the entry in the sideop list) you can use that to see where the list sections from one sorted flag type to the next. quest_uiShow_Active="Active" - "Default is Show. Sideops that are Active are the ones actually currently in play and start when you arrive in the sideop area. Independent of Cleared. You normally wouldn't set this setting to Hide." quest_uiShow_Cleared="Cleared" - "Default is Show. Quests that have been completed." quest_uiShow_Uncleared="Uncleared" - "Quests that have not been completed." quest_uiShow_Activable="Activable" - Only shows those sideops in the selection for being Activated (which includes Active). Usually the best setting to show what sideops are being considered depending on all the underlying conditions and IH settings." quest_uiShow_Open="Open" - "Will try and show all Open sideops, which is usually every sideop as soon as they are introduced through game progression. Most likely to hit the UI limit entries when a lot of addon sideops are installed." quest_uiSort_Active="Sort Active" - Top|Bottom quest_uiSort_Cleared="Sort Cleared" - Top|Bottom quest_uiSort_Uncleared="Sort Uncleared" - Top|Bottom quest_uiSort_Activable="Sort Activable" - Top|Bottom quest_uiSort_Open="Sort Open" - Top|Bottom quest_uiSort_category="Sort by Category" - Ascending|Descending - "The base game sideops are more or less ordered by category already, however addon sideops are added to the end, this sorts by a similar order but puts Story sideops first." quest_uiSort_locationId="Sort by Location" - Ascending|Descending - quest_uiSort_questArea="Sort by sideop area" - Ascending|Descending -"Each main location of the game (Afgh, Africa) is sectioned into about 8 sideops areas to stop overlap and manage loading. You can look at the sideop index to clarify where the list goes from one area to the next (the numbers within an area will be increasing, then be lower for the first sideop in another area). You may want to use in combination with Sort by Location." Sideops selection menu: ivars renamed from sideop_ to quest_category_ Settings changed from OFF, ON to "ALL","NONE","ADDON_ONLY" This lets you have per catergory selection of only Addon sidops. Quest Addon questInfo: Added canActiveQuest=function(questName) --Optional. All quests repop by default (are available to repeat), returning false will stop it from being considered for selection. Use this if you need to stop the quest from Active selection after the initial canOpenQuest. ###### r259 - 2023-02-18 --------------------- Fix: InfPositions Loadposistions, thanks cap for fix. Fix: ih_mission_states not restoring, thanks cap for report. Fix: Start missions on foot hang on mission end if MB demo triggered. This is an anchient bug that has unfortunately likely been around almost as long as IH itself. Thanks OldBoss for the report and save files. Fix: profiles breaking due to help strings with newlines. thanks EntranceJew for the report. InfLookup: A lot of Message signatures, and general message logging rework from EntranceJew Debugging: Bunch of flow logging to get a better understanding of infinite loading screen, though the answer there is most often just 'the exe is loading stuff and it didn't like something aboout one of the data files' Fix: InfWeather losing addon weather info on script reload, thanks EntranceJew for the report. Fix: appearanceDebugMenu fova ivars fix. They still don't do anything, but they were breaking the menu. And due to the exe crash issue after applying several times it's still of limited use. Thanks retali8 for the report. Fix: 'Warp to last user marker' and Warp body to FreeCam position remove announcelog spam that queues when warping - Thanks SinovialVermin8 for the report Fix: 'Support heli to marker' failing with '#coords.pos~=3' warning. Seems feature was broken as of last version - thanks Dr Solus for the report. InfMotion|Motions menu > motionWarpToOrig|"Warp to original position after play". Since some animations move player position through geometry this may help to recover - thanks caplag for implementation. Mission Addons: various features that were limited to vanilla freeroam now work in addons. IH Saves: ih_mission_states, ih_quest_states, ih_priority_staff now only save if the related features are used. So possibly slight better performance when saving. Existing files will still hang around even if they have no meaningful data in them though. Are also now checked to see if they exist first before loading them, so no potentially confusing error message it used to log when trying to load them reguardless. InfAutoDoc: Features and Options docs using same option type indicator as menus. Commands no longer show a 0-1 range. Debugging and dev stuff: ivars GettSettingText calls wrapped in PCallDebug, and log a warning to make it easier to track down broken functions (and not have them break the menu). Some loading logging, probably won't catch much since most load hangs will be inside exe and lua will just be waiting for TppMission.CanStart which will never come. InfCore.PCall correct multi returns - thanks EntranceJew for the method. PCall and LoadExternalModule announceLogs errors. Disclaimer: ------------------------------ Use the mod at you own risk (which can be mitigated by backing up saves and files replaced by mod) Often options may have limited testing, so detailed feedback at Nexus page welcome. Installation ------------------------------ See ['Installation.md'](https://github.com/kapuragu/InfiniteHeaven/blob/dev/tpp/gamedir-ih/GameDir/mod/docs/Infinite%20Heaven/Installation.md) Troubleshooting ------------------------------ See ['Troubleshooting.md'](https://github.com/kapuragu/InfiniteHeaven/blob/dev/tpp/gamedir-ih/GameDir/mod/docs/Infinite%20Heaven/Troubleshooting.md) Usage: ------------------------------ Opening the Infinite Heaven menu: While in ACC Heli (Safe-space menu), or in-mission (In-mission menu) Press and hold [Switch Zoom] (V key or RStick click) then press [Dash] (shift key or LStick click) to toggle the mod menu when in the ACC or in-mission. Or hold [Evade] (Space key or X button) This can be disabled (via IH system menu > Disable hold menu toggle) if it interferes with your play, the above key combo will still work. Also, if IHHook is installed, press F3 key. Using the menu: If you do not have IHHook installed, or IHExt enabled, Infinite Heaven menu will use the Announce Log on the lower left, which is very limited due to its update rate. While the menu it will repeatedly show just the current menu option. Description of menu items: `[#]: [Option name] [Effect/ item type symbol] [Setting value or description] ` Example: `4: Mobility level = 2:Grade 2` Effect symbols: `<>` Applies change or command when setting is selected/cycled to `>>` Applies change or command when pressing [Action] Menu item type symbols: `>` Sub-menu `>>` Command `>]` Command that closes menu when done Some settings apply when selected or just set the value when the a feature is triggered by another command or during mission load. While menu is open: Use Arrow keys or Dpad to navigate the menu or Hold [Switch Zoom] (V key or RStick click) and press movement keys or Left Stick to navigate the menu. Up/Down to select option. Left/Right to change setting or open sub menu. The size/step of the setting change can be made bigger/smaller by holding [Ready weapon] or [Fire]. Press [Binoculars] button to reset current setting. Press [Reload] button to set the current setting to it's minimum. Press [Change Stance] button to go to previous menu. By design I try to keep the initial install to all regular game settings and only changed via infinite heavens in-game mod menu. All settings are reset to off on doing a FOB mission. But I suggest you play offline while the mod is installed. Snakebite mod manager allows easy toggling of mods. Quick Menu: A way to quickly trigger certain Infinite Heaven commands. (Must be enabled via option in IH system menu, or by editing InfQuickMenuDefs.lua) When enabled hold the [Call Radio] (Q key or Left bumper) button then hold one of the following: [Ready weapon] (Right mouse or Left Trigger) to warp to last placed usermarker [Fire] (Left mouse or Right Trigger) to open the menu to heli-to last usermarker (a kludge, but necesary to activate the inter landingzone ride on heli) [Action] (E key or Y button) to activate TSM [Reload] (R key or B button) to activate Free cam [Dash] (Shift or Left stick click) to activate Static camera [Change Stance] (C key or A button) to have Quiet move to last usermarker Profiles: Profiles are lists of settings for IH options, can be used as an alternative, or in conjunction with IHs in-game menu. See MGS_TPP\mod\profiles\All_Options_Example.lua for more info. Settings save file: IH writes its settings to ih_save.lua in the MGS_TPP\mod\saves folder. While the file is editable, editing an inMission save is likely to cause issues. Known Issues ------------------------------ See ['FAQ Known issues.md'](https://github.com/kapuragu/InfiniteHeaven/blob/dev/tpp/gamedir-ih/GameDir/mod/docs/Infinite%20Heaven/FAQ%20Known%20issues.md) Thanks: ------------------------------ Kojima Productions for the great game Sergeanur for qartool atvaark for your fox tools ThreeSocks3 for finding the custom text output for Announce log emoose for cracking lua in fpks jRavens for your early testing Shigu for your specific testing and discussions Topher for your great mod manager Snakebite NasaNhak for your voluminous questions and suggestions unknown123 for the MGSV research. sai for FoxLib and further MGSV research. Morbidslinky for creating Side Ops companion and his poking at the quest system as well as his work on Snakebite. Various people for their donations, including: Domenico, Jeong, Lee, Nicholas, Gary, Joseph, Lisa, Надежда, Ian, WolfJack, Oliver, Daniel, Domenico, Ryuta. Thanks a lot. All the mod users on nexus for trying the mod and bug reports. All the other MGSV mod authors past and current for adding to the community. You for reading this. ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfButton.lua` ```lua --InfButton.lua --tex GOTCHA: the games elapsed time (via GetRawElapsedTimeSinceStartUp) actually runs to the game time update rate, which means it decreases/slows down during highspeedcam/slowmo -- which means InfButtons (and other Inf stuff using GetElapsed) wont really respond during slowmo -- unfortunately os.time only resturns in seconds which isn't enough granularity -- currently using os.clock, GOTCHA: os.clock wraps at ~4,294 seconds --GOTCHA: TODO RESEARCH: I there may be some game states where PlayerVars.scannedButtonsDirect is not updated by the exe, yet normal calls to lua Update continue --most clear example being when doing player 'warp to latest marker' (before code was changed to close menu, disable that WORKAROUND to test) via activating via pressing right menu. --button.RIGHT on PlayerVars.scannedButtonsDirect will remain set, but lua updates continue, which will repeatedly trigger the menu option via InfMenu.Update till scannedButtonsDirect is updated again after TppPlayer.Warp is done --one solution would be to see if there's some unique flag/status set during TppPlayer.Warp to check, ideally something generic/that covers other states so that you can skip InfMenu.Update while its on -- NODEPS local this={} --LOCALOPT: local bit=bit local band=bit.band local Time=Time local PlayerVars=PlayerVars local pairs=pairs local ipairs=ipairs --CULL local ElapsedTime=Time.GetRawElapsedTimeSinceStartUp local ElapsedTime=os.clock--tex using os.clock since Time.GetRawElapsedTimeSinceStartUp is affected by game time scale (highspeedcam etc) GOTCHA: os.clock wraps at ~4,294 seconds local maxIncrementMult=50 local defaultIncrementMult=1 --STATE: this.incrementMultIncrementMult=1.5--tex i r good at naming local currentIncrementMult=defaultIncrementMult this.buttonStates={} --buttonMasks: expanded alternative to PlayerPad --these masks are folded back into InfButton, so you can use them like InfButton.DASH etc --DOC: \BUTTON BITMASKS.TXT \buttonmasks.ods --tex Note: There were a bunch of bitmasks not identified in PlayerPad.. --Currently either labeled UNKOWN, as seen from the existing codes it's possible that there's multiple buttons triggering/sharing one bitmask, so even after 'finding it' copy off instead of rename --fox engine seems to be using bitops http://bitop.luajit.org --converted in-place from index to mask in TABESETUP below --GOTCHA: there's some identical button masks here, most are just what PlayerPad has, but there's a couple I just added as alternate names. this.buttonMasks={ DECIDE=0, STANCE=1, DASH=2, HOLD=3,--tex ready weapon READY_WEAPON=3,--tex added as friendly alternate to HOLD (based on what its called in games Controls menu) FIRE=4, ACTION=5, MOVE_ACTION=5, JUMP=5, RIDE_ON=5, RIDE_OFF=5, CQC_KNIFE_KILL=5, RELOAD=6, CARRY=6, STOCK=7,--tex recenter cam ZOOM_CHANGE=7, VEHICLE_CHANGE_SIGHT=7, MB_DEVICE=8, CALL=9, INTERROGATE=9, SUBJECT=10,--tex tap:recenter 3rd person view, hold: binoculars/First person view --TODO see if trigger on binocular, and see if disable butttons is the same BINOCULARS=10,--tex added as friendly alternate to SUBJECT (based on what its called in games Controls menu) UP=11, PRIMARY_WEAPON=11, DOWN=12, SECONDARY_WEAPON=12, LEFT=13, RIGHT=14, VEHICLE_LIGHT_SWITCH=14, VEHICLE_TOGGLE_WEAPON=14, CQC=15, SIDE_ROLL=16, LIGHT_SWITCH=17, EVADE=18, DIVE=18,--tex added as friendly alternate to EVADE (based on what its called in games Controls menu) SKILL=18,--SSD VEHICLE_FIRE=19, VEHICLE_CALL=20, VEHICLE_DASH=21, BUTTON_PLACE_MARKER=22, PLACE_MARKER=22, ESCAPE=23,--tex Not in PlayerPad, own name. Keyboard UNKNOWN7=24,--tex triggered with Space/Quick dive, and RS click (which also triggers zoom change, stock, decide,vehicle_change_sight), don't know what it's supposed to indicate -- WALK=25,--tex not in PlayerPad, Keyboard UNKNOWN9=26, UNKNOWN10=27, UNKNOWN11=28, UNKNOWN12=29, UNKNOWN13=30, --tex Also in PlayerPad, for PlayerVars.leftStickDirect, rightbleh --don't where triggers are, wouldnt think scannedButtonsDirect STICK_L=0, STICK_R=1, TRIGGER_BREAK=0, TRIGGER_ACCEL=1, TRIGGER_L=0, TRIGGER_R=1, --MAX=2^31,--tex max_int=(2^31)-1, guess at a sane enough limit, though should check bitops to figure out actual. } this.NONE=0 this.ALL=-1 local buttonMasks=this.buttonMasks --TABLESETUP: buttonMasks --tex convert mask index to bitmask for name,maskIndex in pairs(buttonMasks) do buttonMasks[name]=2^maskIndex end --TABLESETUP: InfButton --tex for convenience of external reference fold back into this./InfButton for name,maskIndex in pairs(buttonMasks) do this[name]=maskIndex end --TABLESETUP: buttonStates REFACTOR: check which vars dont really need to be per button local buttonStates=this.buttonStates for name,buttonMask in pairs(buttonMasks) do local buttonState={} buttonStates[buttonMask]=buttonState buttonState.name=name buttonState.isPressed=false buttonState.holdTime=0.9--REFACTOR: this vs repeatrate buttonState.repeatRate=0.85 --button.minRate=0.3 buttonState.decrement=0 buttonState.repeatStart=0--tex used for repeat buttonState.heldStart=0--tex delay between actually being pressed and considered being 'held' buttonState.onHoldStart=0--tex as above, but resets end function this.DEBUG_PrintPressed() if Ivars and Ivars.printPressedButtons:Is(1) then for name,buttonMask in pairs(buttonMasks) do if this.OnButtonDown(buttonMask) then --TppUiCommand.AnnounceLogView("scannedButtonsDirect: ".. PlayerVars.scannedButtonsDirect) --TppUiCommand.AnnounceLogView("scannedButtons: ".. PlayerVars.scannedButtons) --if band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask then--DEBUG: scannedbuttons instead of direct InfCore.Log(name.."="..buttonMask,true) end end end end function this.UpdateHeld() local elapsedTime=ElapsedTime() for name,buttonMask in pairs(buttonMasks) do local buttonState=buttonStates[buttonMask] if (band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask) then --OnButtonDown if not buttonState.isPressed then buttonState.repeatStart=elapsedTime if buttonState.holdTime~=0 then buttonState.heldStart=elapsedTime buttonState.onHoldStart=elapsedTime end end else buttonState.repeatStart=0 buttonState.heldStart=0 buttonState.onHoldStart=0 end end end function this.UpdatePressed() this.DEBUG_PrintPressed() local scannedButtonsDirect=PlayerVars.scannedButtonsDirect for name,buttonMask in pairs(buttonMasks) do buttonStates[buttonMask].isPressed=band(scannedButtonsDirect,buttonMask)==buttonMask--this.ButtonDown(buttonMask) end end function this.UpdateRepeatReset() for name,buttonMask in pairs(buttonMasks) do local buttonState=buttonStates[buttonMask] if buttonState.decrement~=0 then if buttonState.isPressed and not (band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask) then--tex OnButtonUp reset currentIncrementMult=defaultIncrementMult end end end end function this.ButtonDown(buttonMask) -- if band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask then -- TppUiCommand.AnnounceLogView("ButtonDown:" .. buttonMask)--tex DEBUG -- end return band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask end --tex GOTCHA: OnButton functions will have a gameframe of latency, sorry to dissapoint all the pro gamers function this.OnButtonDown(buttonMask) return not buttonStates[buttonMask].isPressed and (band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask) end function this.OnButtonUp(buttonMask) return buttonStates[buttonMask].isPressed and not (band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask) end --tex button has been down for holdTime function this.ButtonHeld(buttonMask) local buttonState=buttonStates[buttonMask] if buttonState.holdTime~=0 and buttonState.heldStart~=0 then if buttonState.isPressed then if ElapsedTime()>buttonState.heldStart+buttonState.holdTime then return true end end end return false end --tex button has been down for holdTime, only true once per hold function this.OnButtonHoldTime(buttonMask) local buttonState=buttonStates[buttonMask] if buttonState.isPressed then if buttonState.holdTime~=0 and buttonState.onHoldStart~=0 then if ElapsedTime()>buttonState.onHoldStart+buttonState.holdTime then buttonState.onHoldStart=0 return true end end end return false end function this.OnButtonRepeat(buttonMask) local buttonState=buttonStates[buttonMask] if buttonState.decrement~=0 then if buttonState.isPressed and not (band(PlayerVars.scannedButtonsDirect,buttonMask)==buttonMask) then--tex OnButtonUp reset --buttonState.repeatRate=buttonState.holdTime currentIncrementMult=defaultIncrementMult return false end --local repeatRate=buttonState.repeatRate --if repeatRate~=0 and if buttonState.repeatStart~=0 then if ElapsedTime()-buttonState.repeatStart>buttonState.repeatRate then buttonState.repeatStart=ElapsedTime() --OFF: if repeatRate > buttonState.minRate then -- repeatRate=repeatRate-buttonState.decrement -- end -- buttonState.repeatRate=repeatRate--]] currentIncrementMult=currentIncrementMult*this.incrementMultIncrementMult --TppUiCommand.AnnounceLogView("DBG:MNU: currentIncrementMult:".. currentIncrementMult)--tex DEBUG if currentIncrementMult>maxIncrementMult then currentIncrementMult=maxIncrementMult end return true end end end return false end function this.GetRepeatMult() return currentIncrementMult end --tex all buttons in combo must be held TODO: some weirdness with trying to check multiple combos function this.OnComboActive(combo) local comboActive=true for i,button in ipairs(combo)do if not this.ButtonHeld(button) then comboActive=false break end end if comboActive then for i,button in ipairs(combo)do buttonStates[button].heldStart=0 end return true end return false end function this.ResetRepeat(buttonMask) local buttonState=buttonStates[buttonMask] buttonState.repeatStart=0 buttonState.heldStart=0 buttonState.onHoldStart=0 end return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfExtToMgsv.lua` ```lua --InfExtToMgsv.lua local this={} local InfCore=InfCore local IHH=IHH local ivars=ivars local Split=InfCore.Split local concat=table.concat local tonumber=tonumber local ipairs=ipairs this.debugModule=false this.updateOutsideGame=true local menuLine="menuLine" local menuItems="menuItems" local OPTION="OPTION" function this.Update(currentChecks,currentTime,execChecks,execState) if IHH then --tex always run if IHH DEBUGNOW else if ivars.enableIHExt==0 then return end --tex no commands from IHExt will be processed unless menu is open if currentChecks.inMenu==false and InfCore.extSession~=0 then --tex dont return if IHExt still hasnt acked the commands if InfCore.mgsvToExtComplete>=InfCore.mgsvToExtCount then if this.debugModule then --InfCore.Log("commands not done",false,true)--DEBUG end return end end end--if not IHH this.ProcessCommands() this.ProcessMenuCommands() end function this.ProcessCommands() local extToMgsvPrev=InfCore.extToMgsvComplete local extPrevSession=InfCore.extSession local ignoreError=true--tex will get 'Domain error' due to file locking between ihext local messages if IHH then messages=IHH.GetPipeInMessages() --messages={}--DEBUGNOW if not messages then return end --tex WORKAROUND, IHH doesn't need this at all, but there's still a bunch of checks using it --DEBUGNOW InfCore.mgsvToExtComplete=InfCore.mgsvToExtCount else --tex legacy text IPC, IHExt only messages=InfCore.GetLines(InfCore.toMgsvCmdsFilePath,ignoreError) if not messages then if this.debugModule then InfCore.Log('Could not read ih_toMgsvCmds') end return elseif #messages==0 then--tex file read ok, but is blank because ihext hasnt started --InfCore.Log('#lines==0-------------') return end end--Get messages for i,message in ipairs(messages)do if message:len()>0 then local args=Split(message,'|') local messageId=tonumber(args[1]) --DEBUGNOW if #args>0 then --tex 1st line command arg3 is extToMgsvComplete --can't just stick it in a command as that would just create a loop of further commands to update it if i==1 and not IHH then --tex |cmdToExtCompletedIndex| --sessionid not really needed as thats set via its own command InfCore.mgsvToExtComplete=tonumber(args[3]) elseif IHH or messageId>InfCore.extToMgsvComplete then if this.debugModule then InfCore.PrintInspect(args,'ToMgsv command '..args[1])--DEBUG end local cmd=args[2] local Command=this.commands[args[2]] if Command==nil then InfCore.Log("WARNING: InfExtToMgsv.ProcessCommands: could not find command "..tostring(args[2])) else Command(args) end InfCore.extToMgsvComplete=messageId end end--end if args>0 end--end for lines end--for messages if not IHH then if InfCore.extSession~=0 then local extToMgsvPrev=InfCore.extToMgsvComplete local extPrevSession=InfCore.extSession if InfCore.extSession~=extPrevSession then --InfCore.Log("SessionChange",false,true)--DEBUG InfCore.WriteToExtTxt()--tex to ack session change, possibly already handled by below, and above, but there may have been an edge case? should have commented it then lol elseif InfCore.extToMgsvComplete~=extToMgsvPrev then --InfCore.Log("extToMgsvComplete change",false,true)--DEBUG InfCore.WriteToExtTxt() end end end--not IHH end--ProcessCommands --IHH IHMenu function this.ProcessMenuCommands() --InfCore.Log("ProcessMenuCommands")--DEBUGNOW if not IHH or not IHH.menuInitialized then return end local messages=IHH.GetMenuMessages() if not messages then return end for i,message in ipairs(messages)do if this.debugModule then InfCore.Log("Process menuMessage: "..message) end if message:len()>0 then message="1|"..message--WORKAROUND: commands still expect my IPC accounting with messageID at the start local args=Split(message,'|') --local messageId=tonumber(args[1]) if #args>0 then if this.commands[args[2]] then this.commands[args[2]](args) end end end end end--ProcessMenuCommands --commands --tex all commands take in single param and array of args --args[1] = messageId (not really useful for a command) --args[2] = command name (ditto) --args[3+] = args as string --tex First message sent by IHExt when it starts --args int extSession function this.ExtSession(args) local extSession = args[3] if extSession~=InfCore.extSession then InfCore.Log('IHExt session changed') InfCore.extSession=extSession if not IHH then InfCore.ExtCmd('SessionChange')--tex a bit of nothing to get the mgsvTpExtComplete to update from the message, ext does likewise InfCore.WriteToExtTxt() end end --DEBUGNOW if InfCore.manualIHExtStart then InfMenu.GoBackTop() InfMenu.DisplayCurrentSetting() --end end --menu commands> --args string elementName, string input --tex handles input from inputLine or menuSetting --TODO document what the actual commands are function this.Input(args) local InfMenu=InfMenu local elementName=args[3] local input=args[4] if (elementName=="inputLine" or elementName=="menuSetting") and input then local currentOption if InfMenu.currentMenuOptions then currentOption=InfMenu.GetCurrentOption() end if currentOption==nil then return end local commandArgs=InfUtil.Split(input,' ') if #commandArgs>0 then if commandArgs[1]=='>' then if currentOption.optionType==OPTION then InfMenu.NextSetting() InfMenu.DisplayCurrentSetting() end elseif commandArgs[1]=='<' then if currentOption.optionType==OPTION then InfMenu.PreviousSetting() InfMenu.DisplayCurrentSetting() end elseif commandArgs[1]=='<<' then InfMenu.GoBackCurrent() InfMenu.DisplayCurrentSetting() elseif commandArgs[1]=='>>' then if not commandArgs[2] then else local menuIndex=tonumber(commandArgs[2]) if menuIndex and menuIndex>0 and menuIndex<=#InfMenu.currentMenuOptions then InfMenu.currentIndex=menuIndex InfMenu.GetSetting() InfMenu.DisplayCurrentSetting() end end -- elseif commandArgs[1]=='?' then -- if currentOption then -- local helpText=InfMenu.GetCurrentHelpText() -- InfCore.ExtCmd('print',helpText) -- InfCore.WriteToExtTxt() -- InfMenu.DisplayCurrentSetting() -- end else if currentOption.optionType==OPTION then if currentOption.OnInput then currentOption:OnInput(input) else local setting=tonumber(input) or input IvarProc.SetSetting(currentOption,setting) InfMenu.DisplayCurrentSetting() end end end--if optionType end--commandArgs end--elementName==inputLine or menuSetting end--function Input --args string listName, int selectedIndex function this.Selected(args) local InfMenu=InfMenu --DEBUGNOW TODO some kind of list registry and subscription to event i guess --just hardcoded to menu for now if args[3]==menuItems then local menuIndex=tonumber(args[4])+1 if menuIndex and menuIndex>0 and menuIndex<=#InfMenu.currentMenuOptions then InfMenu.currentIndex=menuIndex InfMenu.GetSetting() InfMenu.DisplaySetting(menuIndex) end end end --args string comboName, int selectedIndex function this.SelectedCombo(args) local InfMenu=InfMenu --DEBUGNOW TODO some kind of list registry and subscription to event i guess --just hardcoded to menu for now if args[3]=="menuSetting" then local currentOption=InfMenu.GetCurrentOption() if currentOption and currentOption.optionType==OPTION then local setting=tonumber(args[4]) if currentOption.settingIsValue then setting=currentOption.settings[setting+1] end if this.debugModule then InfCore.Log("currentoption:"..currentOption.name.." setting:"..tostring(setting))--DEBUG end currentOption:Set(setting) --InfMenu.GetSetting()--tex ClearCombo and AddCombo overkill, and interferes with doing other stuff IHHook side InfMenu.DisplayCurrentSetting() end end end--SelectedCombo --args string listName, int selectedIndex function this.Activate(args) --TODO, see Selected -^- if args[3]==menuItems then local menuIndex=tonumber(args[4])+1--tex shift to 1 indexed if menuIndex and menuIndex>0 and menuIndex<=#InfMenu.currentMenuOptions then local optionRef=InfMenu.currentMenuOptions[menuIndex] local option=InfMenu.GetOptionFromRef(optionRef) if option then if option.options then--tex is menu InfMenu.GoMenu(option) elseif type(option.OnActivate)=="function" then InfCore.PCallDebug(option.OnActivate,option,ivars[option.name]) elseif option.optionType==OPTION then InfMenu.NextSetting() else InfMenu.SetCurrent() end InfMenu.DisplayCurrentSetting() end end end end--Activate --DEBUGNOW CULL --function this.ToggleMenu(args) -- if #args>2 and args[3]=="1" then--tex KLUDGE DEBUGNOW -- InfCore.Log("ToggleMenu 1") -- if InfMenu.menuOn then -- InfMenu.MenuOff() -- else -- InfMgsvToExt.HideMenu()--tex KLUDGE DEBUGNOW -- end -- return -- end -- -- local currentChecks=InfMain.UpdateExecChecks(InfMain.execChecks) -- InfMenu.ToggleMenu(currentChecks) --end function this.ToggleMenu(args) if InfMenu.currentMenuOptions==nil then--tex WORKAROUND menu not inited yet return end local currentChecks=InfMain.UpdateExecChecks(InfMain.execChecks) InfMenu.ToggleMenu(currentChecks) end function this.MenuOff(args) if InfMenu.menuOn then InfMenu.MenuOff() else InfMgsvToExt.HideMenu()--tex KLUDGE DEBUGNOW end end --args string textBlockName function this.GotKeyboardFocus(args) if args[3]==menuLine then if InfMenu.currentMenu~=InfMenuDefs.searchMenu then InfMenuDefs.searchMenu=InfMenu.BuildMenuDefForSearch(nil) InfMenuDefs.BuildMenuItem("searchMenu",InfMenuDefs.searchMenu) InfMenu.GoMenu(InfMenuDefs.searchMenu) --InfMenu.DisplayCurrentSetting() --DEBUGNOW -- local settingText=this.GetSettingText(optionIndex,option,optionNameOnly) -- local noItemIndicator=false -- local settingNumberOnly=true -- local menuLineText=this.GetSettingText(optionIndex,option,optionNameOnly,noItemIndicator,settingNumberOnly) -- InfMgsvToExt.SetMenuLine(settingText,menuLineText) local settingText="" local menuLineText=InfLangProc.LangString"type_to_search" InfMgsvToExt.SetMenuLine(settingText,menuLineText) InfCore.ExtCmd("SelectAllText",menuLine) InfCore.WriteToExtTxt() end end end --args string textBlockName, string searchText function this.EnterText(args) if args[3]==menuLine then local searchText=args[4] InfMenuDefs.searchMenu=InfMenu.BuildMenuDefForSearch(searchText) InfMenuDefs.BuildMenuItem("searchMenu",InfMenuDefs.searchMenu) InfMenu.GoMenu(InfMenuDefs.searchMenu) --local settingText="" --local menuLineText=InfLangProc.LangString"type_to_search" --InfMgsvToExt.SetMenuLine(settingText,menuLineText) --InfCore.WriteToExtTxt() end end --< menu commands --SIDE: whatever the script does lol function this.DoScript(args) local luaString=args[3] InfCore.Log("DoString:"..luaString)--DEBUGNOW TODO: limit to level:trace local chunk,err=loadstring(luaString) if not chunk then InfCore.Log(tostring(err)) InfCore.ExtCmd("DoScriptError",tostring(err))--DEBUGNOW IMPLEMENT else chunk(); end end--DoScript --tex called via IH IPC to register more commands for IH IPC to be able to call (which in themselves may send callback commands) --IN/SIDE: InfExtToMgsv.commands function this.RegisterToGameCmd(args) local cmdName=args[3] local cmdString=args[4] InfCore.Log("RegisterToGameCmd: "..cmdName..":"..cmdString)--DEBUGNOW TODO: limit to level:trace local registerString="InfExtToMgsv.commands['"..cmdName.."']=function(args)"..cmdString.."end" local chunk,err=loadstring(registerString) if not chunk then InfCore.Log(tostring(err)) InfCore.ExtCmd("DoScriptError",tostring(err))--DEBUGNOW IMPLEMENT else chunk(); end end--RegisterToGameCmd --FoxKitToMgsv function this.GetPlayerPos(args) --InfCore.Log("GetPlayerPos")--DEBUG --DEBUGNOW -- local offsetY=0 -- if Ivars.adjustCameraUpdate:Is(0) then--tex freecam not on -- offsetY=-0.783 -- if PlayerInfo.OrCheckStatus{PlayerStatus.CRAWL} then -- offsetY = offsetY + 0.45 -- end -- end --InfCore.Log("GetPlayerPos "..tostring(vars.playerPosX)..","..tostring(vars.playerPosY))--DEBUGNOW InfCore.ExtCmd('GamePlayerPos',vars.playerPosX,vars.playerPosY,vars.playerPosZ,vars.playerRotY) end --SetPlayerPos|{x}|{y}|{z}|{yaw} function this.SetPlayerPos(args) local x,y,z,yaw=args[3],args[4],args[5],args[6] TppPlayer.Warp{pos={x,y,z},rotY=yaw} end function this.GetCameraPos(args) InfCore.ExtCmd('GameCameraPos', vars.playerCameraPosition[0],vars.playerCameraPosition[1],vars.playerCameraPosition[2], vars.playerCameraRotation[0],vars.playerCameraRotation[1],vars.playerCameraRotation[2]) end --SetCameraPos|{x}|{y}|{z}|{pitch}|{yaw} function this.SetCameraPos(args) local x,y,z,pitch,yaw=args[3],args[4],args[5],args[6],args[7] local currentCamName=this.GetCurrentCamName() local currentPos=Vector3(x,y,z) InfCamera.WritePosition(currentCamName,currentPos) end function this.GetUserMarkerPos(args) local markerIndex=tonumber(args[3]) local markerPos=InfUserMarker.GetMarkerPosition(markerIndex) if markerPos then InfCore.ExtCmd('UserMarkerPos',markerIndex,markerPos[1],markerPos[2],markerPos[3]) end end this.commands={ extSession=this.ExtSession, --menu commands> input=this.Input, selected=this.Selected, selectedcombo=this.SelectedCombo, activate=this.Activate, togglemenu=this.ToggleMenu, menuoff=this.MenuOff, GotKeyboardFocus=this.GotKeyboardFocus, EnterText=this.EnterText, --< DoScript=this.DoScript, RegisterToGameCmd=this.RegisterToGameCmd, --FoxKitToMgsv GetPlayerPos=this.GetPlayerPos, SetPlayerPos=this.SetPlayerPos, GetCameraPos=this.GetCameraPos, SetCameraPos=this.SetCameraPos, GetUserMarkerPos=this.GetUserMarkerPos, } return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfHooks.lua` ```lua -- DOBUILD: 1 -- InfHooks.lua -- tex also search HOOK in other modules for some manual hooks -- Currently most hooks (and direct calls to IH from base game modules functions) go through InfMain > IH modules -- Upside of this is you can look at InfMain to see of the hooks -- Downside is its a bit monolythic/at a glance not iding the original function module, -- but the alternate would probably be to have Inf versions of all the base game files, which is kind of already true for a lot of the main ones. local this={} local InfCore=InfCore local PCall=InfCore.PCall local Log=InfCore.Log local LogFlow=InfCore.LogFlow local pack2=InfUtil.pack2 local GetArgsString=InfUtil.GetArgsString local GetPCallReturnsStrings=InfUtil.GetPCallReturnsStrings local stringFormat=string.format local unpack=unpack local concat=table.concat this.debugHooksEnabled=false --tex GOTCHA be cautious when hooking scripts that can be reloaded (like scriptblocks or IH external modules) --scriptblocks are also more likely to cause yield across c-call boundary. --pre-hooking (calling your hook function before the original function) will allow multiple-return functions to return as normal --can also isolate your hook function in a pcall to isolate it crashing from impacting the original function --TODO: rework so these are just InfMain.CallOnModules since most are currently just a passthrough for that this.hookFuncs={ TppSave={ VarRestoreOnMissionStart=function() InfCore.LogFlow("InfHook TppSave.VarRestoreOnMissionStart") this.TppSave.VarRestoreOnMissionStart() InfMain.PostVarRestoreOnMissionStart() end, VarRestoreOnContinueFromCheckPoint=function() InfCore.LogFlow("InfHook TppSave.VarRestoreOnContinueFromCheckPoint") this.TppSave.VarRestoreOnContinueFromCheckPoint() InfMain.PostVarRestoreOnContinueFromCheckPoint() end, --VarSaveOnlyGlobalData=function() VarSave=function(missionCode,isMissionStart) InfCore.LogFlow("InfHook TppSave.VarSave") InfMain.VarSave(missionCode,isMissionStart) this.TppSave.VarSave(missionCode,isMissionStart) end, -- VarSaveOnRetry=function()--DEBUGNOW when this called? -- VarSaveMbMangement=function(missionCode,forceSave) -- VarSaveQuest=function(missionCode) -- VarSaveConfig=function() -- VarSaveMGO=function() -- VarSavePersonalData=function() DoSave=function(saveParams,force) InfCore.LogFlow("InfHook TppSave.DoSave force:"..tostring(force))--tex dosave is either through the following SaveData functions directly(rarely, you see the logging of that function directly above if it is) or enqued by the same functions (where youll see the function names logged higher up) to happen next Update > ProcessSaveQueue or OnAllocate > WaitingAllEnqueuedSaveOnStartMission if TppSave.debugModule then InfCore.PrintInspect(saveParams,"TppSave.DoSave saveParams") end local saveResult=this.TppSave.DoSave(saveParams,force) --OFF IvarProc.OnSave(saveParams,force)--tex hookin on this level catches savepersonaldata called in init_sequence can throw spanner in works for some of the stuff we want to do during load, so hooking InfCore.LogFlow("/InfHook TppSave.DoSave done")--tex just to make it clear that function completed and not an issue, since this is often one of the last things logged on load hangs. return saveResult end, --tex all DoSave or EnqueSave (which triggers DoSave MakeNewGameSaveData=function(acquirePrivilegeInTitleScreen) InfCore.LogFlow("InfHook TppSave.MakeNewGameSaveData acquirePrivilegeInTitleScreen:"..tostring(acquirePrivilegeInTitleScreen)) InfMain.MakeNewGameSaveData(acquirePrivilegeInTitleScreen) this.TppSave.MakeNewGameSaveData(acquirePrivilegeInTitleScreen) end, SaveGameData=function(missionCode,needIcon,doSaveFunc,reserveNextMissionStartSave,isCheckPoint) InfCore.LogFlow("InfHook TppSave.SaveGameData") InfMain.SaveGameData(missionCode,needIcon,doSaveFunc,reserveNextMissionStartSave,isCheckPoint)--tex don't know if want before or after this.TppSave.SaveGameData(missionCode,needIcon,doSaveFunc,reserveNextMissionStartSave,isCheckPoint) end, SaveConfigData=function(needIcon,doSave,reserveNextMissionStart) InfCore.LogFlow("InfHook TppSave.SaveConfigData") this.TppSave.SaveConfigData(needIcon,doSave,reserveNextMissionStart) end, SavePersonalData=function(needIcon,doSave,reserveNextMissionStartSave) InfCore.LogFlow("InfHook TppSave.SavePersonalData") this.TppSave.SavePersonalData(needIcon,doSave,reserveNextMissionStartSave) end, --tex no go for some reason. -- LoadGameDataFromSaveFile=function(area) -- return InfCore.PCall(function(area)--DEBUG -- --InfCore.LogFlow("InfHook TppSave.LoadGameDataFromSaveFile("..tostring(area)..")") -- local loadResult=this.LoadGameDataFromSaveFile(area) -- return loadResult -- end,area)--DEBUG -- end, }, TppSequence={ RegisterSequenceTable=function(sequences)--tex hooking just for debugging at the moment, since TppSequence not in buils, also since it builds mvars.seq_sequenceTable could futz with that if needs be InfCore.LogFlow("InfHook TppSequence.RegisterSequenceTable") this.TppSequence.RegisterSequenceTable(sequences) if TppSequence.debugModule then InfCore.PrintInspect(mvars.seq_sequenceNames,"mvars.seq_sequenceNames") InfCore.PrintInspect(sequences,"_sequence sequences") end end, SetNextSequence=function(sequenceName,params) --tex another case from the orig function that might be good to log if mvars.seq_sequenceNames then local sequenceId=mvars.seq_sequenceNames[sequenceName] if sequenceId==nil then InfCore.Log("WARNING: TppSequence.SetNextSequence: could not find sequenceName in mvars.seq_sequenceNames: "..tostring(sequenceName).." in ") return end end --tex aparently this is sometimes called when svars aint up? --or at the least TppMission.CheckMissionState guards this case if svars==nil then InfCore.Log("WARNING: TppSequence.SetNextSequence: called but svars==nil") return end local currentId=svars.seq_sequence local prevName="" if currentId then prevName=TppSequence.GetSequenceNameWithIndex(currentId) end InfCore.Log("TppSequence.SetNextSequence from "..prevName.." to "..sequenceName) this.TppSequence.SetNextSequence(sequenceName,params) end, }, TppRanking={ UpdateShootingPracticeClearTime=function(questNameAsRankingCategory,scoreTime) InfShootingPractice.UpdateShootingPracticeClearTime(questNameAsRankingCategory,scoreTime) end, },--TppRanking }--hookFuncs --tex stuff I'm just flow logging and pcalling in debug mode, TODO: unify with hookFuncs? this.debugPCallHooks={ TppAnimal={ OnActivateQuest=true, OnDeactivateQuest=true, CheckQuestAllTarget=true, }, TppClock={ RegisterClockMessage=true, }, TppEnemy={ OnActivateQuest=true, OnDeactivateQuest=true, CheckQuestAllTarget=true, DefineSoldiers=true, SetUpSoldiers=true, ClearDDParameter=true, PrepareDDParameter=true, SetUpDDParameter=true, ChangeRouteUsingGimmick=true,--tex TODO see if/when this hits }, TppGimmick={ OnActivateQuest=true, OnDeactivateQuest=true, CheckQuestAllTarget=true, }, TppMission={ ExecuteMissionFinalize=true, MissionFinalize=true, MissionGameEnd=true, OnCanMissionClear=true, ReserveMissionClear=true, UpdateObjective=true, ShowUpdateObjective=true, SafeStopSettingOnMissionReload=true, }, TppQuest={ OnMissionGameEnd=true, UnloadCurrentQuestBlock=true, UpdateActiveQuest=true, }, TppResult={ GetMbMissionListParameterTable=true, }, TppRevenge={ SetUpEnemy=true, }, TppScriptBlock={ InitScriptBlockState=true, FinalizeScriptBlockState=true, ActivateScriptBlockState=true, DeactivateScriptBlockState=true, RequestActivate=true, Load=true, Unload=true, SaveScriptBlockId=true, PreloadRequestOnMissionStart=true, PreloadSettingOnMissionStart=true, ReloadScriptBlock=true, ResolveSavedScriptBlockInfo=true, }, TppSequence={ ReserveNextSequence=true, }, TppUI={ FadeIn=true, FadeOut=true, }, }--debugPCallHooks function this.GetFunction(moduleName,functionName) local originalModule=_G[moduleName] local originalFunction=nil if not originalModule then InfCore.Log("WARNING: InfHooks.GetFunction could not find module:"..tostring(moduleName),false,true) else originalFunction=originalModule[functionName] if originalFunction==nil then InfCore.Log("WARNING: InfHooks.GetFunction could not find function:"..moduleName.."."..tostring(functionName),false,true) end end return originalModule,originalFunction end function this.AddHook(moduleName,functionName,hookFunction) local originalModule,originalFunction=this.GetFunction(moduleName,functionName) if originalModule and originalFunction then if type(hookFunction)~="function" then InfCore.Log("ERROR: InfHook.AddHook("..moduleName..","..functionName..") hookFunc is not a function") else this[moduleName]=this[moduleName]or{}--tex existing or new local moduleHooks=this[moduleName] if moduleHooks[functionName] then --tex TODO: just swap out existing hook using original, but log a warning InfCore.Log("ERROR: InfHook.AddHook: attempting to add a hook to a previously hooked function: "..moduleName.."."..functionName) else moduleHooks[functionName]=originalFunction--tex save original function ref originalModule[functionName]=hookFunction--tex override end end end end function this.RemoveHook(moduleName,functionName) local hookedModule,hookedFunction=this.GetFunction(moduleName,functionName) if hookedModule and hookedFunction then local moduleHooks=this[moduleName] if not moduleHooks then InfCore.Log("ERROR: InfHook.RemoveHook cannot find any hooks for module: "..moduleName.."."..functionName) else local originalFunction=moduleHooks[functionName] if not originalFunction then InfCore.Log("ERROR: InfHook.RemoveHook cannot find any hook for function: "..moduleName.."."..functionName) else hookedModule[functionName]=originalFunction--tex restore moduleHooks[functionName]=nil--tex clear end end end end --UNUSED function this.CreatePreHookShim(moduleName,functionName,hookFunction) local originalModule,originalFunction=this.GetFunction(moduleName,functionName) if not originalModule or not originalFunction then return nil end local ShimFunction=function(...) hookFunction(...) return originalFunction(...) end return ShimFunction end--CreatePreHookShim --tex for wrapping a function in PCall and giving an LogFlow call --GOTCHA: this tail call setup means names will be eaten in stack dump function this.CreateDebugWrap(moduleName,functionName) local flowFmt="%s.%s(%s) [InfHook Pre]" local originalModule,originalFunction=this.GetFunction(moduleName,functionName) if not originalModule or not originalFunction then return nil end --upvalue opt local argsStrings={}--tex we can reuse table since we are getting args length local ShimFunction=function(...) local argsString=GetArgsString(argsStrings,...) LogFlow(stringFormat(flowFmt,moduleName,functionName,argsString)) return PCall(originalFunction,...) end return ShimFunction end--CreateDebugWrap function this.SetupDebugHooks() InfCore.LogFlow("InfHooks.SetupDebugHooks:") if not this.debugHooksEnabled then this.debugHooksEnabled=true this.AddDebugHooks(this.debugPCallHooks,true) end end --hookDef table format: this.debugPCallHooks function this.AddDebugHooks(hookDef,enable) InfCore.LogFlow("InfHooks.AddDebugHooks:") for moduleName,moduleHooks in pairs(hookDef)do for functionName,hookInfo in pairs(moduleHooks)do if hookInfo==true then local originalModule,originalFunction=this.GetFunction(moduleName,functionName) if originalModule and originalFunction then if enable then local wrapper=this.CreateDebugWrap(moduleName,functionName) this.AddHook(moduleName,functionName,wrapper) else this.RemoveHook(moduleName,functionName) end end end end end end --IN: this.hookFuncs function this.AddHooks() InfCore.LogFlow("InfHooks.AddHooks:") for moduleName,moduleHooks in pairs(this.hookFuncs)do for functionName,hookFunction in pairs(moduleHooks)do this.AddHook(moduleName,functionName,hookFunction) end end end--AddHooks --this.AnnounceLogView=TppUiCommand.AnnounceLogView --TppUiCommand.AnnounceLogView=function(message) -- this.AnnounceLogView(message) --end -- --this.AnnounceLogViewLangId=TppUiCommand.AnnounceLogViewLangId --TppUiCommand.AnnounceLogViewLangId=function(...) -- this.AnnounceLogViewLangId(...) --end -- --this.AnnounceLogViewJoinLangId=TppUiCommand.AnnounceLogViewJoinLangId --TppUiCommand.AnnounceLogViewJoinLangId=function(...) -- this.AnnounceLogViewJoinLangId(...) --end return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfMainTpp.lua` ```lua -- InfMainTpp.lua --tex tpp specific stuff and stuff I've shifted out of lib\InfMain local this={} local InfMain=InfMain local InfUtil=InfUtil local ipairs=ipairs local pairs=pairs local GetTypeIndex=GameObject.GetTypeIndex local SendCommand=GameObject.SendCommand this.reserveSoldierNames={} this.soldierPool={} this.emptyCpPool={} this.lrrpDefines={}--tex from AddLrrps this.MAX_STAFF_NUM_ON_CLUSTER=18--DYNAMIC onallocate--LIMIT --reserve soldierpool --tex number of soldier locators in fox2s this.reserveSoldierCounts={ [30010]=40, [30020]=40, [30050]=140, } this.packages={ [30010]={ "/Assets/tpp/pack/mission2/ih/ih_soldier_loc_free.fpk",--DEPENDENCY mission fox2 Soldier2GameObject totalCount }, [30020]={ "/Assets/tpp/pack/mission2/ih/ih_soldier_loc_free.fpk"--DEPENDENCY mission fox2 Soldier2GameObject totalCount }, } this.soldierTypeForSubtypes={ DD_A=EnemyType.TYPE_DD, DD_PW=EnemyType.TYPE_DD, DD_FOB=EnemyType.TYPE_DD, SKULL_CYPR=EnemyType.TYPE_SKULL, SKULL_AFGH=EnemyType.TYPE_SKULL, SOVIET_A=EnemyType.TYPE_SOVIET, SOVIET_B=EnemyType.TYPE_SOVIET, PF_A=EnemyType.TYPE_PF, PF_B=EnemyType.TYPE_PF, PF_C=EnemyType.TYPE_PF, CHILD_A=EnemyType.TYPE_CHILD, } --tex a few demo files force their own snake heads which naturally goes badly if DD female and use current soldier in cutscenes this.noSkipIsSnakeOnly={--tex> Demo_Funeral=true,--PATCHUP: shining lights end cinematic forces snake head with ash --volgin recovery quest, demo forces snake head with bandages Demo_RecoverVolgin=true, p31_080100_000_final=true, } function this.PostModuleReload(prevModule) this.reserveSoldierNames=prevModule.reserveSoldierNames this.soldierPool=prevModule.soldierPool this.emptyCpPool=prevModule.emptyCpPool this.lrrpDefines=prevModule.lrrpDefines end function this.PreMissionLoad(missionId,currentMissionId) this.ClearXRay()--DEBUGNOW DEBUGNOW test this still works now that premissionload is before fova funcs end function this.OnAllocateTop(missionTable) --DEBUGNOW TODO: needed here or can be shifted to OnAllocate? this.MAX_STAFF_NUM_ON_CLUSTER=18--SYNC f30050_sequence if Ivars.mbAdditionalSoldiers:Is()>0 then this.MAX_STAFF_NUM_ON_CLUSTER=36 end end function this.OnAllocate(missionTable) --local equipOnTrucks=Ivars.vehiclePatrolProfile:EnabledForMission() and Ivars.vehiclePatrolTruckEnable:Is(1) and Ivars.putEquipOnTrucks:Is(1) --tex not sure how TppPickable.DropItem is implemented, but bunging it in case it creates locators. --local increasedWeapons=IvarProc.EnabledForMission("customWeaponTable") or Ivars.itemDropChance:Is()>0 or Ivars.enableWildCardFreeRoam:EnabledForMission() or equipOnTrucks --REF TppPlayer.OnAllocate --e3 demos (380*1024)*24}--=9338880 -- nearly 5x the max retail block size --TppDefine.DEFAULT_EQUIP_MISSION_BLOCK_GROUP_SIZE = 1677721, --sequence.EQUIP_MISSION_BLOCK_GROUP_SIZE= max 1887437 (s10054) -- if(missionTable and missionTable.sequence)and missionTable.sequence.EQUIP_MISSION_BLOCK_GROUP_SIZE then -- mvars.ply_equipMissionBlockGroupSize=missionTable.sequence.EQUIP_MISSION_BLOCK_GROUP_SIZE -- else -- mvars.ply_equipMissionBlockGroupSize=TppDefine.DEFAULT_EQUIP_MISSION_BLOCK_GROUP_SIZE -- end -- if(missionTable and missionTable.sequence)and missionTable.sequence.MAX_PICKABLE_LOCATOR_COUNT then -- mvars.ply_maxPickableLocatorCount=missionTable.sequence.MAX_PICKABLE_LOCATOR_COUNT--free = 100,100,64 (afgh,mafr,mtbs) -- else -- mvars.ply_maxPickableLocatorCount=TppDefine.PICKABLE_MAX--16 -- end -- if(missionTable and missionTable.sequence)and missionTable.sequence.MAX_PLACED_LOCATOR_COUNT then -- mvars.ply_maxPlacedLocatorCount=missionTable.sequence.MAX_PLACED_LOCATOR_COUNT--free = 200,220,128 -- else -- mvars.ply_maxPlacedLocatorCount=TppDefine.PLACED_MAX--8 -- end --tex seems to be hitting pickable limit in afgh free even with all IH features off? weird. --if increasedWeapons then --FLOW: set: TppMain.OnAllocate (before ih modules .OnAllocate) > TppPlayer.OnAllocate --FLOW: usage: TppMain.OnAllocate (near end / after modules .OnAllocate) > TppPlayer.SetMaxPickableLocatorCount > TppPickable.OnAllocate mvars.ply_maxPlacedLocatorCount=mvars.ply_maxPlacedLocatorCount+20 mvars.ply_maxPickableLocatorCount=mvars.ply_maxPickableLocatorCount+100 mvars.ply_equipMissionBlockGroupSize=mvars.ply_equipMissionBlockGroupSize*2 --end end function this.MissionPrepare() --tex OFF if TppMission.IsStoryMission(vars.missionCode) then if Ivars.gameOverOnDiscovery:Is(1) then TppMission.RegistDiscoveryGameOver() end --end end function this.OnInitializeTop(missionTable) if InfMain.IsOnlineMission(vars.missionCode)then return end --tex modify missionTable before it's acted on if missionTable.enemy then local enemyTable=missionTable.enemy if Tpp.IsTypeTable(enemyTable.soldierDefine) then if not InfMain.IsContinue() then local numReserveSoldiers=this.reserveSoldierCounts[vars.missionCode] or 0 this.reserveSoldierNames=InfUtil.GenerateNameList("sol_ih_%04d",numReserveSoldiers) this.soldierPool=InfTppUtil.ResetObjectPool("TppSoldier2",this.reserveSoldierNames) this.emptyCpPool=InfMain.BuildEmptyCpPool(enemyTable.soldierDefine) this.lrrpDefines={} InfWalkerGear.walkerPool=InfTppUtil.ResetObjectPool("TppCommonWalkerGear2",InfWalkerGear.walkerNames) InfWalkerGear.mvar_walkerInfo={} -- InfCore.Log("numReserveSoldiers:"..numReserveSoldiers)--tex DEBUG -- InfCore.PrintInspect(this.reserveSoldierNames,"reserveSoldierNames")--tex DEBUG -- InfCore.PrintInspect(this.soldierPool,"soldierPool")--tex DEBUG end InfCore.PCallDebug(InfSoldier.ModMissionTableTop,missionTable,this.emptyCpPool)--DEBUG InfCore.PCallDebug(InfVehicle.ModifyVehiclePatrol,enemyTable.VEHICLE_SPAWN_LIST,enemyTable.soldierDefine,enemyTable.travelPlans,this.emptyCpPool) enemyTable.soldierTypes=enemyTable.soldierTypes or {} enemyTable.soldierSubTypes=enemyTable.soldierSubTypes or {} enemyTable.soldierPowerSettings=enemyTable.soldierPowerSettings or {} enemyTable.soldierPersonalAbilitySettings=enemyTable.soldierPersonalAbilitySettings or {} InfCore.PCallDebug(InfSoldier.AddLrrps,enemyTable.soldierDefine,enemyTable.travelPlans,this.lrrpDefines,this.emptyCpPool) InfCore.PCallDebug(InfWalkerGear.AddLrrpWalkers,this.lrrpDefines,InfWalkerGear.walkerPool) InfCore.PCallDebug(InfSoldier.ModifyLrrpSoldiers,enemyTable.soldierDefine,this.soldierPool) InfCore.PCallDebug(InfSoldier.AddWildCards,enemyTable.soldierDefine,enemyTable.soldierSubTypes,enemyTable.soldierPowerSettings,enemyTable.soldierPersonalAbilitySettings) InfCore.PCallDebug(InfSoldier.ModMissionTableBottom,missionTable,this.emptyCpPool)--DEBUG --tex DEBUG unassign soldiers from vehicle lrrp so you dont have to chase driving vehicles local ejectVehiclesSoldiers=false if ejectVehiclesSoldiers then for cpName,cpDefine in pairs(enemyTable.soldierDefine)do cpDefine.lrrpVehicle=nil end end end end end function this.Init(missionTable) this.messageExecTable=Tpp.MakeMessageExecTable(this.Messages()) this.ModifyMinesAndDecoys() if (vars.missionCode==30050 --[[WIP or vars.missionCode==30250--]]) and Ivars.mbEnableFultonAddStaff:Is(1) then mvars.trm_isAlwaysDirectAddStaff=false end end function this.AddMissionPacks(missionCode,packPaths) local packages=this.packages[missionCode] if packages then for i,packPath in ipairs(packages) do packPaths[#packPaths+1]=packPath end end end function this.OnMissionCanStart(currentChecks) --tex WORKAROUND invasion mode extract from mb weirdness, just disable for now -- if Ivars.mbWarGamesProfile:Is"INVASION" and vars.missionCode==30050 then -- Player.SetItemLevel(TppEquip.EQP_IT_Fulton_WormHole,0) -- end local locationName=TppLocation.GetLocationName() if Ivars.disableLzs:Is"ASSAULT" then InfLZ.DisableLzs(TppLandingZone.assaultLzs[locationName]) elseif Ivars.disableLzs:Is"REGULAR" then InfLZ.DisableLzs(TppLandingZone.missionLzs[locationName]) end if Ivars.disableOutOfBoundsChecks:Is(1) then mvars.mis_ignoreAlertOfMissionArea=true local trapName="trap_mission_failed_area" local enable=false TppTrap.ChangeNormalTrapState(trapName,enable) TppTrap.ChangeTriggerTrapState(trapName,enable) end end function this.OnReload(missionTable) this.messageExecTable=Tpp.MakeMessageExecTable(this.Messages()) end function this.OnMessage(sender,messageId,arg0,arg1,arg2,arg3,strLogText) Tpp.DoMessage(this.messageExecTable,TppMission.CheckMessageOption,sender,messageId,arg0,arg1,arg2,arg3,strLogText) end function this.Messages() return Tpp.StrCode32Table{ GameObject={ {msg="Damage",func=this.OnDamage}, }, Player={ {msg="FinishOpeningDemoOnHeli",func=this.ClearMarkers},--tex xray effect off doesn't stick if done on an endfadein, possibly because TppMission FinishOpeningDemoOnHeli listener clears status on them, messages calls seem to respect registration order? (TppMission messages are registered before IH modules ?) so slapping this on same msg seems to work VERIFY }, Timer={ {msg="Finish",sender="Timer_WaitStartingGame",func=this.OnGameStart}, }, UI={ {msg="EndFadeIn",sender="FadeInOnGameStart",func=this.FadeInOnGameStart},--fires on: most mission starts, on-foot free and story missions, not mb on-foot, but does mb heli start {msg="EndFadeIn",sender="FadeInOnStartMissionGame",func=this.FadeInOnGameStart},--fires on: returning to heli from mission {msg="EndFadeIn",sender="OnEndGameStartFadeIn",func=this.FadeInOnGameStart},--fires on: on-foot mother base, both initial and continue }, } end --TODO: VERIFY, add vehicle machineguns local vehicleAttacks={ [TppDamage.ATK_VehicleHit]=true, [TppDamage.ATK_Tankgun_20mmAutoCannon]=true, [TppDamage.ATK_Tankgun_30mmAutoCannon]=true, [TppDamage.ATK_Tankgun_105mmRifledBoreGun]=true, [TppDamage.ATK_Tankgun_120mmSmoothBoreGun]=true, [TppDamage.ATK_Tankgun_125mmSmoothBoreGun]=true, [TppDamage.ATK_Tankgun_82mmRocketPoweredProjectile]=true, [TppDamage.ATK_Tankgun_30mmAutoCannon]=true, [TppDamage.ATK_Wav1]=true, [TppDamage.ATK_WavCannon]=true, [TppDamage.ATK_TankCannon]=true, [TppDamage.ATK_WavRocket]=true, [TppDamage.ATK_HeliMiniGun]=true, [TppDamage.ATK_HeliChainGun]=true, } function this.OnDamage(gameId,attackId,attackerId) local typeIndex=GetTypeIndex(gameId) if typeIndex~=TppGameObject.GAME_OBJECT_TYPE_SOLDIER2 then--and typeIndex~=TppGameObject.GAME_OBJECT_TYPE_HELI2 then return end if Tpp.IsPlayer(attackerId) then --InfCore.DebugPrint"OnDamage attacked by player" local soldierAlertOnHeavyVehicleDamage=Ivars.soldierAlertOnHeavyVehicleDamage:Get() if soldierAlertOnHeavyVehicleDamage>0 then if vehicleAttacks[attackId] then --InfCore.DebugPrint"OnDamage AttackIsVehicle" for cpId,soldierIds in pairs(mvars.ene_soldierIDList)do--tex TODO:find or build a better soldierid>cpid lookup if soldierIds[gameId]~=nil then if TppEnemy.GetPhaseByCPID(cpId)0 then--tex -- InfCore.DebugPrint"cpAlertOnVehicleFulton>0"--DEBUG -- local riderIdArray=SendCommand(vehicleId,{id="GetRiderId"}) -- for seatIndex,riderId in ipairs(riderIdArray) do -- if seatIndex==1 then -- if riderId~=NULL_ID then -- InfCore.DebugPrint"vehicle has driver"--DEBUG -- for cpId,soldierIds in pairs(mvars.ene_soldierIDList)do -- if soldierIds[riderId]~=nil then -- InfCore.DebugPrint"found rider cp"--DEBUG -- if TppEnemy.GetPhaseByCPID(cpId)0 then --TODO: check against developed --local currentLevel=Player.GetItemLevel(equip) --InfCore.DebugPrint(itemIvar.name..":"..itemIvar.setting)--DEBUG --tex levels = grades in dev menu, so 1=off since there's no grade 1 for these --rlc nope! grade 0 is off for the sonar! was everything else off here? Player.SetItemLevel(itemIvar.equipId,itemIvar:Get()-1) end end --tex fulton does not disable at grade 0, will be overridden if wormhole grade > 0. if Ivars.itemLevelFulton:Is()>0 then --TODO: check against developed --REF local currentLevel=Player.GetItemLevel(equip) Player.SetItemLevel(Ivars.itemLevelFulton.equipId,Ivars.itemLevelFulton:Get()) end --tex wormhole grade 0 = disabled/use fulton grade, > 0 = enabled if Ivars.itemLevelWormhole:Is()>0 then --TODO: check against developed --REF local currentLevel=Player.GetItemLevel(equip) --tex levels = 0 off, 1 on, but since ivar uses 0 as default, shift by 1. Player.SetItemLevel(Ivars.itemLevelWormhole.equipId,Ivars.itemLevelWormhole:Get()-1) end --tex pretty normal grade wise (code could be merged with hands), grade 0 does not disable. local itemLevelIvars={ Ivars.itemLevelIntScope, Ivars.itemLevelIDroid, } for i,itemIvar in ipairs(itemLevelIvars) do if itemIvar:Is()>0 then --TODO: check against developed --local currentLevel=Player.GetItemLevel(equip) --InfCore.DebugPrint(itemIvar.name..":"..itemIvar.setting)--DEBUG --tex levels = grades in dev menu, so itemlevel 0=off, but shifting since ivar 0 = dont set. Player.SetItemLevel(itemIvar.equipId,itemIvar:Get()) end end if TppMission.IsSubsistenceMission()then return end if Ivars.setSubsistenceSuit:Is(1) then local playerSettings={partsType=PlayerPartsType.NORMAL,camoType=PlayerCamoType.OLIVEDRAB,handEquip=TppEquip.EQP_HAND_NORMAL,faceEquipId=0} TppPlayer.RegisterTemporaryPlayerType(playerSettings) end if Ivars.setDefaultHand:Is(1) then mvars.ply_isExistTempPlayerType=true mvars.ply_tempPlayerHandEquip={handEquip=TppEquip.EQP_HAND_NORMAL} end --tex bail on free<>mission to preserver your equip --tex not MB local free={ [30010]=true, [30020]=true, } if not Ivars.prevMissionCode then return end if Ivars.dontOverrideFreeLoadout:Is(1) then if (free[Ivars.prevMissionCode] and TppMission.IsStoryMission(vars.missionCode)) or (free[vars.missionCode] and TppMission.IsStoryMission(Ivars.prevMissionCode)) then return end end local ospIvars={ Ivars.primaryWeaponOsp, Ivars.secondaryWeaponOsp, Ivars.tertiaryWeaponOsp, Ivars.clearSupportItems, Ivars.clearItems, } for i,ivar in ipairs(ospIvars) do if igvars.inf_event==false then IvarProc.UpdateSettingFromGvar(ivar) end local initSetting=ivar:GetTableSetting() if initSetting then if ivar==Ivars.clearItems then TppPlayer.SetInitItems(initSetting,true) else TppPlayer.SetInitWeapons(initSetting,true) end end end end --UTIL TODO: maybe actually move to util function this.WarpRat(gameObjectName,pos,rotY) local gameObjectId={type="TppRat",index=0} local route="" local command={id="Warp",name=gameObjectName,ratIndex=0,position=pos,degreeRotationY=rotY,route=route,nodeIndex=0} GameObject.SendCommand(gameObjectId,command) end --tex used to counteract sendcommand IsDD in TppHero / stop dd staff killed message and point loss --TODO: not handling hostage --(ie should be used in conjunction with, unless you want to throw a IsDD or maybe EnemyType.TYPE_DD in) --currently only used in TppHero to manage mb_staff_died --currently soldiers only --REF base game DD soldiers --MB Free soldiers - your staff --Fob soldiers: --defense - your staff --attack - enemy dd --Fob Hostages, but only on event? --10115 : Mission 22 - Retake the Platform --soldiers - enemy dd --hostages - your staff --10240 : shining lights -- your staff --some sideops hostages (which ones exactly?) - your staff --sideops wandering mb soldiers - your staff --sendcommand IsDD seems to return true when soldierType==EnemyType.TYPE_DD -- VERIFY --seems to have an exception (return false) for 10115 soldiers VERIFY --what about FOB? --DEBUGNOW reworked to just assume anything outside of vanilla usage for DD soldiers means DD are from an addon where DD type is used as enemy, --but may eventually want authors to more explicitly define if the DD type is enemy or staff function this.IsDDEnemy(gameId) local missionCode=vars.missionCode if TppMission.IsFOBMission(missionCode) then return false end --if Tpp.IsHostage(gameId)then --elseif Tpp.IsSoldier(gameId)then--tex current usage already has this check --tex mb invasion TODO can't remember if I mbqf can have invasion lol if (missionCode==30050 or missionCode==30250) and Ivars.mbNonStaff:Is(0) then return false end if missionCode==10240 then return false end if true then return true end ----- if missionCode==10115 then return true--tex see note about IsDD exception above end if Ivars.customSoldierTypeFREE:Is()>0 and Ivars.customSoldierTypeFREE:MissionCheck() then return true end if Ivars.customSoldierTypeMISSION:Is()>0 and Ivars.customSoldierTypeMISSION:MissionCheck() then return true end --tex problem: some community sideops (caplags for ex) set the soldier type to TYPE_DD to have the soldiers talk english (VERIFY some languae setting in exe keying off soldierType?) --and to workaround some soldierType>weaponIdTable issues. --TODO some way of indicating quest soldier is enemy --but the recover old msf soldiers quests are actual legit flagged as TYPE_DD so also need to cover that --would take a bit of effort to get working as: --(unless I've missed something) currently there's no way of knowing if soldier is part of a quest using just their gameId --(the assumption that they are only sol_quest* soldiers isn't always true for some sideops that use existing soldiers) --the best spot to flag them as ddenemy would be the quest script, but that also has no easy way to reference --KLUDGE, as above TODO is not implemented if TppMission.IsFreeMission(missionCode)and(missionCode~=30050 and missionCode~=30250)then local soldierType=mvars.ene_soldierTypes[gameId] if soldierType==EnemyType.TYPE_DD then local subType=mvars.ene_soldierSubType[gameId] --ASSUMPTION: vanilla game TYPE_DD at this point (which should only be quest dd wandering soldiers) have correct sub type (looks like they don't have explicitly set subtype but default to DD_A) --so incorrect subtype would be from a community sideop if not InfEneFova.IsSubTypeCorrectForType(soldierType)then return true end end end--isFreeMission --tex revisit this if you ever fix wildcard females --if Ivars.enableWildCardFreeRoam:Is()>0 and Ivars.enableWildCardFreeRoam:MissionCheck() then --(would need an is female check) -- return --end --end--hostage or soldier end -- mb dd equip --tex TODO: don't like how this is still tied up both with weapon table and .GetMbs ranks --CULL and all uses off local enableDDEquipStr="enableDDEquip" function this.IsDDEquip(missionId) local missionCode=missionId or vars.missionCode if not InfMain.IsOnlineMission(missionId) and missionCode >5 then--tex IsFreeMission hangs on startup? TODO retest return IvarProc.EnabledForMission(enableDDEquipStr) end return false end function this.GetMbsClusterSecuritySoldierEquipGrade(missionId)--SYNC: soldierEquipGrade local missionCode=missionId or vars.missionCode local grade = TppMotherBaseManagement.GetMbsClusterSecuritySoldierEquipGrade{} if this.IsDDEquip(missionCode) then InfMain.RandomSetToLevelSeed() grade=InfMain.MinMaxIvarRandom"soldierEquipGrade" InfMain.RandomResetToOsTime() end --TppUiCommand.AnnounceLogView("GetEquipGrade: gvar:".. Ivars.soldierEquipGrade:Get() .." grade: ".. grade)--DEBUG --TppUiCommand.AnnounceLogView("Caller: ".. tostring(debug.getinfo(2).name) .." ".. tostring(debug.getinfo(2).source))--DEBUG return grade end function this.GetMbsClusterSecuritySoldierEquipRange(missionId) local missionCode=missionId or vars.missionCode if this.IsDDEquip(missionCode) then if Ivars.mbSoldierEquipRange:Is"RANDOM" then return math.random(0,2)--REF:{ "FOB_ShortRange", "FOB_MiddleRange", "FOB_LongRange", }, but range index from 0 else return Ivars.mbSoldierEquipRange:Get() end end return TppMotherBaseManagement.GetMbsClusterSecuritySoldierEquipRange() end function this.GetMbsClusterSecurityIsNoKillMode(missionId) local missionCode=missionId or vars.missionCode if this.IsDDEquip(missionCode) then return Ivars.mbDDEquipNonLethal:Is(1) end return TppMotherBaseManagement.GetMbsClusterSecurityIsNoKillMode() end function this.GetAverageRevengeLevel() local stealthLevel=TppRevenge.GetRevengeLv(TppRevenge.REVENGE_TYPE.STEALTH) local combatLevel=TppRevenge.GetRevengeLv(TppRevenge.REVENGE_TYPE.COMBAT) return math.floor((stealthLevel+combatLevel)/2) end function this.IsNonCombatMission(missionCode) missionCode=missionCode or vars.missionCode --tex KLUDGE TODO: really need a good way to indicate this is a non combat mission, --also may need to expand this to other stuff (see what else is being called where this function is called, or rather whats calling that) local mbMissions={[30050]=true,[30150]=true,[30250]=true}--tex> if mbMissions[vars.missionCode] and not this.IsMbEvent(vars.missionCode) then return true end end--IsNonCombatMission function this.IsMbEvent(missionCode) missionCode=missionCode or vars.missionCode return missionCode==30050 and (Ivars.mbWarGamesProfile:Is()>0 or igvars.inf_event~=false) end --UTIL CULL not used, if used then should be moved to TppUtil or something function this.IsMBDemoStage() if vars.missionCode~=30050 then return false end return (not TppPackList.IsMissionPackLabel"default") or TppDemo.IsBattleHangerDemo(TppDemo.GetMBDemoName()) end --tex CULL doesn't seem to work, either I'm doing something wrong or the buddy system doesnt use it for mb function this.OverwriteBuddyPosForMb() if TppMission.IsMbFreeMissions(vars.missionCode) and Ivars.mbEnableBuddies:Is(1)then if gvars.heli_missionStartRoute~=0 then local groundStartPosition=InfLZ.GetGroundStartPosition(gvars.heli_missionStartRoute) if groundStartPosition then local mbBuddyEntrySettings={} local pos=Vector3(groundStartPosition.pos[1],groundStartPosition.pos[2],groundStartPosition.pos[3]) local entryEntry={} entryEntry[EntryBuddyType.BUDDY]={pos,0} --entryEntry[EntryBuddyType.VEHICLE]={pos,0} mbBuddyEntrySettings[gvars.heli_missionStartRoute]=entryEntry TppEnemy.NPCEntryPointSetting(mbBuddyEntrySettings) end end end end --CALLER start2nd --tex messy, but lets user still use MbmCommonSetting/EquipDevelopConstSetting/EquipDevelopFlowSetting mods function this.MBManagementSettings() InfCore.LogFlow("InfMainTpp.MBManagementSettings") --MbmCommonSetting TppMotherBaseManagement.RegisterMissionBaseStaffTypes{missionId=30050,staffTypes={2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25}}--tex to give proper stats/skills spread on MB invasion --EquipDevelopConstSetting --key for games NONE lang strings has not been discovered, so adding to IH till/if then. TppMotherBaseManagement.RegCstDev{p00=900,p01=TppEquip.EQP_None,p02=TppMbDev.EQP_DEV_TYPE_Handgun,p03=0,p04=0,p05=65535,p06="equip_none",p07="info_wp_none",p08="/Assets/tpp/ui/texture/EquipIcon/equip_blank_icon_alp",p09=TppMbDev.EQP_DEV_GROUP_WEAPON_010,p10="cmmn_wp_none",p30="cmmn_wp_none",p31=0,p32=0,p33=0,p34=0,p35=0,p36=0} TppMotherBaseManagement.RegCstDev{p00=901,p01=TppEquip.EQP_None,p02=TppMbDev.EQP_DEV_TYPE_Assault,p03=0,p04=0,p05=65535,p06="equip_none",p07="info_wp_none",p08="/Assets/tpp/ui/texture/EquipIcon/equip_blank_icon_alp",p09=TppMbDev.EQP_DEV_GROUP_WEAPON_010,p10="cmmn_wp_none",p30="cmmn_wp_none",p31=0,p32=0,p33=0,p34=0,p35=0,p36=0} --EquipDevelopFlowSetting --tex SYNC p50 to last EquipDevelopFlowSetting entry p50 +1, or to 0 - but that gives a 'Development requirements met' message each startup, even if item is developed. TppMotherBaseManagement.RegFlwDev{p50=920,p51=0,p52=1,p53=0,p54=0,p55=0,p56=0,p57=0,p58="",p59=0,p60="",p61=0,p62=1,p63=0,p64=0,p65="",p66=0,p67="",p68=0,p69=0,p70=0,p71=0,p72=0,p73=0,p74=1} TppMotherBaseManagement.RegFlwDev{p50=921,p51=0,p52=1,p53=0,p54=0,p55=0,p56=0,p57=0,p58="",p59=0,p60="",p61=0,p62=1,p63=0,p64=0,p65="",p66=0,p67="",p68=0,p69=0,p70=0,p71=0,p72=0,p73=0,p74=1} end return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfMenuCommands.lua` ```lua --InfMenuCommands.lua local this={} --tex lines kinda blurry between Commands and Ivars, currently commands arent saved/have no gvar associated --For the majority of Commands you just provide a function, with a capitalized name, but you can also provide other menuItem paramters in table before with non capitalized name, ex menuOffItem, resetSettingsItem --LOCALOPT: local InfCore=InfCore local InfMain=InfMain local NULL_ID=GameObject.NULL_ID local GetGameObjectId=GameObject.GetGameObjectId local SendCommand=GameObject.SendCommand --menu menu items this.menuOffItem={ isMenuOff=true, } this.MenuOffItem=function() InfMenu.MenuOff() InfMenu.currentIndex=1 end this.resetSettingsItem={ isMenuOff=true, } this.ResetSettingsItem=function() InfMenu.ResetSettingsDisplay() --tex to stop announcelog spam I guess if not InfCore.IHExtRunning() then InfMenu.MenuOff() end end this.resetAllSettingsItem={ isMenuOff=true, } this.ResetAllSettingsItem=function() InfMenu.PrintLangId"setting_all_defaults" InfMenu.ResetSettings() IvarProc.PrintNonDefaultVars() InfMenu.PrintLangId"done" InfMenu.MenuOff() end this.GoBackItem=function() InfMenu.GoBackCurrent() end this.GoBackTopItem=function() InfMenu.GoBackTop() end this.GeneralHelpItem=function() InfMenu.PrintLangId("general_help_cmd") end --commands function this.TitleAbortToHeliSpace() InfMain.abortToAcc=true InfMenu.PrintLangId"will_abort_to_acc" end --IHMenu this.showStyleEditor={ requiresIHHook=true, } this.ShowStyleEditor=function() InfCore.MenuCmd'ToggleStyleEditor' end this.showImguiDemo={ requiresIHHook=true, } this.ShowImguiDemo=function() InfCore.MenuCmd'ToggleImguiDemo' end --profiles this.ApplySelectedProfile=function() local profileInfo=Ivars.selectProfile:GetProfileInfo() if profileInfo==nil then InfMenu.PrintLangId"no_profiles_installed" return end InfMenu.PrintLangId"applying_profile" IvarProc.ApplyProfile(profileInfo.profile) end this.ResetSelectedProfile=function() local profileInfo=Ivars.selectProfile:GetProfileInfo() if profileInfo==nil then InfMenu.PrintLangId"no_profiles_installed" return end InfMenu.PrintLangId"applying_profile" IvarProc.ResetProfile(profileInfo.profile) end this.ViewProfile=function() local profileInfo=Ivars.selectProfile:GetProfileInfo() if profileInfo==nil then InfMenu.PrintLangId"no_profiles_installed" return end local profileMenu=InfMenu.BuildProfileMenu(profileInfo.profile) IvarProc.ApplyProfile(profileInfo.profile) InfMenu.GoMenu(profileMenu) end this.RevertProfile=function() --tex revertProfile is built in BuildProfileMenu IvarProc.ApplyProfile(InfMenu.currentMenu.revertProfile) InfMenu.GoBackCurrent() end this.SaveToProfile=function() IvarProc.WriteProfile(true,false) end -- --show stuff this.positions={} this.positionsXML={} this.ShowPosition=function() InfCore.Log"ShowPosition" local blockNameStr32=Tpp.GetLoadedLargeBlock() local blockName=InfLookup.StrCode32ToString(blockNameStr32) or blockNameStr32 or "nil" InfCore.Log("Current large block:"..blockName,false,true) local blockIndexX,blockIndexY=Tpp.GetCurrentStageSmallBlockIndex() InfCore.Log("Current small block index: x:"..blockIndexX..",y:"..blockIndexY,false,true) if TppLocation.GetLocationName()=="mtbs" then local clusterId=MotherBaseStage.GetCurrentCluster() local clusterName=mtbs_cluster.GetCurrentClusterName() local plntName=mtbs_cluster.GetCurrentPlnt() local plntName="plnt0" local clusterCenter=mtbs_cluster.GetDemoCenter(clusterName,plntName) InfCore.Log(clusterId.." "..clusterName.." "..plntName) InfCore.Log("Cluster plnt0 center: "..clusterCenter:GetX()..","..clusterCenter:GetY()..","..clusterCenter:GetZ()) end InfQuest.PrintQuestArea() local offsetY=0-- -0.78 local x,y,z=vars.playerPosX,vars.playerPosY,vars.playerPosZ y=y+offsetY local rotY=vars.playerCameraRotation[1] local positionTable=string.format("{pos={%.3f,%.3f,%.3f},rotY=%.3f,},",x,y,z,rotY) local positionXML=string.format('',x,y,z) table.insert(this.positions,positionTable) table.insert(this.positionsXML,positionXML) -- InfCore.Log(positionTable) -- InfCore.Log(positionXML) InfCore.Log("positions:\n"..table.concat(this.positions,"\n"),false,true) InfCore.Log("positionsxml:\n"..table.concat(this.positionsXML,"\n"),false,true) InfCore.DebugPrint("Position written to ih_log") end this.ShowMissionCode=function() TppUiCommand.AnnounceLogView("MissionCode: "..vars.missionCode)--ADDLANG end this.ShowLangCode=function() local languageCode=AssetConfiguration.GetDefaultCategory"Language" TppUiCommand.AnnounceLogView(InfLangProc.LangString"language_code"..": "..languageCode) end -- -- --this.ResetCameraSettings=function()--CULL -- InfMain.ResetCamPosition() -- InfMenu.PrintLangId"cam_settings_reset" -- end -- this.SetSelectedCpToMarkerClosestCp=function() local lastMarkerIndex=InfUserMarker.GetLastAddedUserMarkerIndex() if lastMarkerIndex==nil then InfCore.DebugPrint("lastMarkerIndex==nil") return end local markerPos=InfUserMarker.GetMarkerPosition(lastMarkerIndex) local cpName=InfMain.GetClosestCp(markerPos) if not cpName then InfCore.Log("Could not find cp",false,true) return end for cpId,currentName in pairs(mvars.ene_cpList)do --InfCore.DebugPrint(tostring(n).." "..tostring(currentName)) if currentName==cpName then Ivars.selectedCp:Set(cpId) InfCore.DebugPrint("selectedCp set to "..cpId..":"..cpName) return end end end this.SetSelectedCpToMarkerObjectCp=function() local lastMarkerIndex=InfUserMarker.GetLastAddedUserMarkerIndex() if lastMarkerIndex==nil then InfCore.DebugPrint("lastMarkerIndex==nil") return end -- InfMain.PrintUserMarker(lastMarkerIndex) -- InfMain.PrintMarkerGameObject(lastMarkerIndex) local gameId=vars.userMarkerGameObjId[lastMarkerIndex] if gameId==nil then InfCore.DebugPrint"gameId==nil" return end local soldierName,cpName=InfLookup.ObjectNameForGameId(gameId) if cpName==nil then InfCore.DebugPrint"cpName==nil" return end for cpId,currentName in pairs(mvars.ene_cpList)do --InfCore.DebugPrint(tostring(n).." "..tostring(currentName)) if currentName==cpName then Ivars.selectedCp:Set(cpId) InfCore.DebugPrint("selectedCp set to "..cpId..":"..cpName) return end end InfCore.DebugPrint(cpName.." not found in ene_cpList") InfCore.PrintInspect(mvars.ene_cpList,{varName="mvars.ene_cpList",announceLog=true,force=true}) end --stageblock this.ResetStageBlockPosition=function() StageBlockCurrentPositionSetter.SetEnable(false) end this.SetStageBlockPositionToMarkerClosest=function() local lastMarkerIndex=InfUserMarker.GetLastAddedUserMarkerIndex() if lastMarkerIndex==nil then InfCore.DebugPrint("lastMarkerIndex==nil") return end local lastMarkerIndex=InfUserMarker.GetLastAddedUserMarkerIndex() if lastMarkerIndex==nil then InfCore.DebugPrint("lastMarkerIndex==nil") else --InfUserMarker.PrintUserMarker(lastMarkerIndex) local x=vars.userMarkerPosX[lastMarkerIndex] local z=vars.userMarkerPosZ[lastMarkerIndex] StageBlockCurrentPositionSetter.SetEnable(true) StageBlockCurrentPositionSetter.SetPosition(x,z) InfCore.Log("StageBlockCurrentPositionSetter.SetPosition("..x..","..z..")",true,true) end end this.SetStageBlockPositionToFreeCam=function() local currentCamName=InfCamera.GetCurrentCamName() local movePosition=InfCamera.ReadPosition(currentCamName) local x,y,z=movePosition:GetX(),movePosition:GetY(),movePosition:GetZ() StageBlockCurrentPositionSetter.SetEnable(true) StageBlockCurrentPositionSetter.SetPosition(x,z) InfCore.Log("StageBlockCurrentPositionSetter.SetPosition("..x..","..z..")",true,true) end --DEBUGNOW jam somewhere this.RefreshPlayer=function() InfCore.DebugPrint("RefreshPlayer") Player.SetWetEffect() Player.ResetDirtyEffect() vars.passageSecondsSinceOutMB=0 end local buddyIndex=1 this.DEBUG_buddyCycle=function() local position=Vector3(vars.playerPosX,vars.playerPosY+1,vars.playerPosZ) local buddyTypes={ --BuddyType.NONE, BuddyType.QUIET, BuddyType.HORSE, BuddyType.DOG, BuddyType.WALKER_GEAR, } local buddyType=buddyTypes[buddyIndex] TppBuddyService.UnsetDisableCallBuddyType(buddyType) TppBuddy2BlockController.CallBuddy(buddyType,position,0) buddyIndex=buddyIndex+1 if buddyIndex>#buddyTypes then buddyIndex=1 end end this.ForceRegenSeed=function() InfMain.RegenSeed(40010,40010) end local skipIvars={ debugMode=true, debugMessages=true, debugMessagesVerbose=true, debugFlow=true, debugOnUpdate=true, adjustCameraUpdate=true, warpPlayerUpdate=true, }--skipIvars --tex randomize (most)all ivars this.DEBUG_RandomizeAllIvars=function() InfCore.Log("DEBUG_RandomizeAllIvars") local ivarNames={} local function IsIvar(ivar)--TYPEID return type(ivar)=="table" and (ivar.range or ivar.settings) end for name,ivar in pairs(Ivars) do if IsIvar(ivar) then if (not skipIvars[name]) and ivar.save and ivar.optionType=="OPTION" then--DEBUGNOW ivar.save ? if not string.find(name,"cam_")then table.insert(ivarNames,name) end end end end InfCore.PrintInspect(ivarNames,"randomize ivarNames") InfCore.Log("#ivarNames="..#ivarNames) local startPos=1 local endPos=#ivarNames for i=startPos,endPos do local name=ivarNames[i] InfCore.Log("RandomizeAllIvars "..i..": "..name) local ivar=Ivars[name] if not ivar.range and not ivar.settings then InfCore.Log("WARNING: DEBUG_RandomizeAllIvars ivar "..tostring(name).. " not ivar.range and not ivar.settings") else local value local min,max=IvarProc.GetRange(ivar) if maxmax then value=ivar.default-increment end InfCore.Log("Setting ivar "..name.." to incremented from default value: "..tostring(value)) ivar:Set(value) end end--DEBUG_SetIvarsToNonDefault --SYNC run PrintIvars on main. this.DEBUG_SetIvarsToDefault=function() InfCore.DebugPrint"DEBUG_SetIvarsToDefault" local ivarNames={} for i,ivarName in pairs(ivarNames) do local ivar=Ivars[ivarName] local currentSetting=ivars[ivarName] if ivar==nil then InfCore.Log(ivarName.."==nil") elseif not ivar.save then --InfCore.DebugPrint(ivarName.." save not set") elseif currentSetting~=ivar.default then InfCore.Log(ivarName..":"..tostring(currentSetting).." not default:"..tostring(ivar.default)..", resetting") IvarProc.SetSetting(ivar,ivar.default) end end end -- this.DEBUG_DropItem=function() local downposition=Vector3(vars.playerPosX,vars.playerPosY+1,vars.playerPosZ) -- TppPickable.DropItem{ -- equipId = TppEquip.EQP_IT_DevelopmentFile, -- number = TppMotherBaseManagementConst.DESIGN_2006, -- position = downposition, -- rotation = Quat.RotationY( 0 ), -- linearVelocity = Vector3(0,2,0),--Vector3( 0, 2, 0 ), -- angularVelocity = Vector3(0,2,0),--Vector3( 0, 2, 0 ) -- } local linearMax=2 local angularMax=14 TppPickable.DropItem{ equipId = TppEquip.EQP_SWP_SmokeGrenade_G01,--EQP_SWP_C4_G04, number = 65535, position = downposition, rotation = Quat.RotationY( 0 ), linearVelocity = Vector3(math.random(-linearMax,linearMax),math.random(-linearMax,linearMax),math.random(-linearMax,linearMax)), angularVelocity = Vector3(math.random(-angularMax,angularMax),math.random(-angularMax,angularMax),math.random(-angularMax,angularMax)), } end this.DEBUG_PrintVarsClock=function() InfCore.DebugPrint("vars.clock:"..vars.clock) end this.DEBUG_InspectAllMenus=function() --InfCore.PrintInspect(InfMenuDefs.allMenus) for n,menu in ipairs(InfMenuDefs.allMenus) do if menu==nil then InfCore.DebugPrint("menu==nil at index "..n) elseif menu.name==nil then InfCore.DebugPrint("menu.name==nil at index "..n) else InfMenu.PrintLangId(menu.name) end end end this.DEBUG_ClearAnnounceLog=function() --TppUiStatusManager.SetStatus("AnnounceLog","INVALID_LOG")--pretty sure this is disable TppUiStatusManager.ClearStatus"AnnounceLog" end this.currentWarpIndex=1 local singleStep=false this.DEBUG_WarpToObject=function() --local objectList=InfMainTpp.reserveSoldierNames -- local travelPlan="travelArea2_01" -- local objectList=InfVehicle.inf_patrolVehicleConvoyInfo[travelPlan] local objectList=InfSoldier.ene_wildCardNames --local objectList=InfParasite.parasiteNames.CAMO --local objectList=InfLookup.truckNames --local objectList={"veh_trc_0000"} --local objectList=InfLookup.jeepNames --local objectList={TppReinforceBlock.REINFORCE_DRIVER_SOLDIER_NAME} --local objectList=TppReinforceBlock.REINFORCE_SOLDIER_NAMES --local objectList=InfInterrogation.interCpQuestSoldiers --local objectList=InfWalkerGear.walkerNames --local objectList=InfNPCHeli.heliList if objectList==nil then InfCore.DebugPrint"objectList nil" return end this.warpObjecList=objectList if #objectList==0 then InfCore.DebugPrint"objectList empty" return end local stepCount=0 local warpPos=Vector3(0,0,0) local objectName="NULL" local function Step() objectName=objectList[this.currentWarpIndex] local gameId=objectName if type(objectName)=="string" then gameId=GameObject.GetGameObjectId(objectName) end if gameId==nil or gameId==GameObject.NULL_ID then InfCore.Log(objectName.." gameId==NULL_ID") warpPos=Vector3(0,0,0) else warpPos=GameObject.SendCommand(gameId,{id="GetPosition"}) if warpPos==nil then InfCore.Log("GetPosition nil for "..objectName,true) return else InfCore.Log(this.currentWarpIndex..":"..objectName.." pos:".. warpPos:GetX()..",".. warpPos:GetY().. ","..warpPos:GetZ(),true) end end this.currentWarpIndex=this.currentWarpIndex+1 if this.currentWarpIndex>#objectList then this.currentWarpIndex=1 end stepCount=stepCount+1 end while (warpPos:GetX()==0 and warpPos:GetY()==0 and warpPos:GetZ()==0) and stepCount<=#objectList do Step() if singleStep then break end end if warpPos:GetX()~=0 or warpPos:GetY()~=0 or warpPos:GetZ()~=0 then InfCore.DebugPrint(objectName.." pos:".. warpPos:GetX()..",".. warpPos:GetY().. ","..warpPos:GetZ()) TppPlayer.Warp{pos={warpPos:GetX(),warpPos:GetY()+1,warpPos:GetZ()},rotY=vars.playerCameraRotation[1]} end end this.DEBUG_PrintObjectListPosition=function() local objectList=InfMainTpp.reserveSoldierNames -- local travelPlan="travelArea2_01" -- local objectList=InfVehicle.inf_patrolVehicleConvoyInfo[travelPlan] --local objectList=InfMain.ene_wildCardNames --local objectList=InfParasite.parasiteNames.CAMO --local objectList=InfLookup.truckNames --local objectList={"veh_trc_0000"} --local objectList=InfLookup.jeepNames --local objectList={TppReinforceBlock.REINFORCE_DRIVER_SOLDIER_NAME} --local objectList=TppReinforceBlock.REINFORCE_SOLDIER_NAMES --local objectList=InfInterrogation.interCpQuestSoldiers --local objectList=InfWalkerGear.walkerNames --local objectList=InfNPCHeli.heliList if objectList==nil then InfCore.DebugPrint"objectList nil" return end this.warpObjecList=objectList if #objectList==0 then InfCore.DebugPrint"objectList empty" return end local stepCount=0 local pos=Vector3(0,0,0) local objectName="NULL" local function Step() objectName=objectList[this.currentWarpIndex] local gameId=objectName if type(objectName)=="string" then gameId=GameObject.GetGameObjectId(objectName) end if gameId==nil or gameId==GameObject.NULL_ID then InfCore.Log(objectName.." gameId==NULL_ID") pos=Vector3(0,0,0) else pos=GameObject.SendCommand(gameId,{id="GetPosition"}) if pos==nil then InfCore.Log("GetPosition nil for "..objectName,true) return else InfCore.Log(this.currentWarpIndex..":"..objectName.." pos:".. pos:GetX()..",".. pos:GetY().. ","..pos:GetZ(),true) end end this.currentWarpIndex=this.currentWarpIndex+1 if this.currentWarpIndex>#objectList then this.currentWarpIndex=1 end stepCount=stepCount+1 end while (pos:GetX()==0 and pos:GetY()==0 and pos:GetZ()==0) and stepCount<=#objectList do Step() if singleStep then break end end end this.loadExternalModules={ isMenuOff=true, } this.LoadExternalModules=function() InfMenu.MenuOff() InfMain.LoadExternalModules(true) end this.CopyLogToPrev=function() local fileName=InfCore.logFileName InfCore.CopyFileToPrev(InfCore.paths.mod,fileName,".txt") InfCore.ClearFile(InfCore.paths.mod,fileName,".txt") end this.SetAllFriendly=function() local function SetFriendly(gameId) SendCommand(gameId,{id="SetFriendly",enabled=true}) end local function SetHeliNoNotice(gameId) SendCommand(gameId,{id="SetEyeMode",mode="Close"}) SendCommand(gameId,{id="SetRestrictNotice",enabled=true}) SendCommand(gameId,{id="SetCombatEnabled",enabled=false}) end InfTppUtil.RunOnAllObjects("TppSoldier2",nil,SetFriendly) local heliInstances=1 if Ivars.attackHeliPatrolsFREE:Is()>0 then heliInstances=5 end InfTppUtil.RunOnAllObjects("TppEnemyHeli",heliInstances,SetHeliNoNotice) for cpId,cpName in pairs(mvars.ene_cpList)do local command={id="SetFriendlyCp"} GameObject.SendCommand(cpId,command) end end--SetAllFriendly this.SetAllZombie=function() local function SetZombie(gameObjectId) local disableDamage=false local isHalf=false local isMsf=false local life=300 local stamina=200 isHalf=isHalf or false SendCommand(gameObjectId,{id="SetZombie",enabled=true,isHalf=isHalf,isZombieSkin=true,isHagure=true,isMsf=isMsf}) SendCommand(gameObjectId,{id="SetMaxLife",life=life,stamina=stamina}) SendCommand(gameObjectId,{id="SetZombieUseRoute",enabled=false}) if disableDamage==true then SendCommand(gameObjectId,{id="SetDisableDamage",life=false,faint=true,sleep=true}) end if isHalf then local ignoreFlag=0 SendCommand(gameObjectId,{id="SetIgnoreDamageAction",flag=ignoreFlag}) end end InfTppUtil.RunOnAllObjects("TppSoldier2",350,SetZombie) end this.CheckPointSave=function() --TppCheckPoint.Update{atCurrentPosition=true} if InfMenu then InfMenu.MenuOff(true) end TppCheckPoint.Update{safetyCurrentPosition=true} end --LEGACY for SOC quickmenu this.ToggleFreeCam=function() InfCamera.ToggleFreeCam() end --< menu commands function this.PostAllModulesLoad() --this.BuildCommandItems() tex: moved to InfMenuDefs PostAllModulesLoad end this.commandItems={} local OPTIONTYPE_COMMAND="COMMAND" local IsTable=function(checkType)return type(checkType)=="table" end--tex removed dependency on Tpp.IsTypeTable local IsFunction=function(checkType)return type(checkType)=="function" end local switchRange={max=1,min=0,increment=1} --tex add menu items for plain functions function this.ItemNameForFunctionName(name) --tex menu item name is function name with lowercase 1st char return name:sub(1,1):lower()..name:sub(2,name:len()) end --build out full item definition --OUT/SIDE ivars function this.BuildCommandItem(Command,name) --tex menu item name is function name with lowercase 1st char local itemName=this.ItemNameForFunctionName(name) local menuItem=this[itemName] or {} if not IsTable(menuItem) then InfCore.Log("WARNING:"..itemName.."is not a table") else menuItem.optionType=OPTIONTYPE_COMMAND menuItem.name=itemName menuItem.default=0 ivars[itemName]=menuItem.default--tex DEBUGNOW TODO remove command dependency on ivar/switching menuItem.range=switchRange--DEBUGNOW settings-no-range menuItem.OnChange=Command end return menuItem,itemName end --IN/SIDE: InfMenuDefs --OUT/SIDE: this.commandItems --Builds menuItems for the commands function this.BuildCommandItems() InfCore.LogFlow("InfMenuCommands.BuildCommandItems") InfUtil.ClearTable(this.commandItems) --tex TODO: have InfMenuDefs use module.registerMenus too? for name,item in pairs(InfMenuDefs) do if IsTable(item) and item.options then --InfCore.Log("item:"..name)--DEBUG for i,optionRef in ipairs(item.options)do --InfCore.Log("optionRef:"..optionRef)--DEBUG local Command,refName=InfCore.GetStringRef(optionRef) if Command and IsFunction(Command) then --InfCore.Log("refName:"..refName)--DEBUG local menuItem,itemName=this.BuildCommandItem(Command,refName) --InfCore.Log("itemName:"..itemName)--DEBUG this.commandItems[itemName]=menuItem end end end end for i,module in ipairs(InfModules) do if module.registerMenus and module~=InfMenuDefs then if this.debugModule then InfCore.PrintInspect(module.registerMenus,module.name..".registerMenus") end for j,name in ipairs(module.registerMenus)do local menuDef=module[name] if not menuDef then InfCore.Log("WARNING: InfMenuCommands.BuildCommandItems: could not find "..name.." in "..module.name) else if menuDef.options then for i,optionRef in ipairs(menuDef.options)do --InfCore.Log("optionRef:"..optionRef)--DEBUG local Command,refName=InfCore.GetStringRef(optionRef) if Command and IsFunction(Command) then --InfCore.Log("refName:"..refName)--DEBUG local menuItem,itemName=this.BuildCommandItem(Command,refName) --InfCore.Log("itemName:"..itemName)--DEBUG this.commandItems[itemName]=menuItem end end end end end--for registermenu end--if registermenu end--for infmodules --InfCore.PrintInspect(this.commandItems,"commandItems")--DEBUG end return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfMgsvToExt.lua` ```lua -- InfMgsvToExt.lua local this={} local InfCore=InfCore local ivars=ivars local ExtCmd=InfCore.ExtCmd local concat=table.concat local type=type this.debugModule=false --FORMAT --uiElements={ -- name={} --} this.uiElements={} function this.PostModuleReload(prevModule) this.uiElements=prevModule.uiElements end function this.Init() --DEBUGNOW -- if vars.missionCode <=5 then -- ExtCmd("UiElementVisible","runningLabel",1) -- else -- ExtCmd("UiElementVisible","runningLabel",0) -- end -- InfCore.WriteToExtTxt() end --mgsvtoext commands --tex creates a ui element from a wpf xaml definition function this.CreateUiElement(name,xamlStr) if this.uiElements[name] then InfCore.Log('WARNING: CreateUiElement: uiElement '..name..' already exists') --DEBUGNOW return end this.uiElements[name]=xamlStr --InfCore.PrintInspect(xamlStr,'xamlStr')--DEBUG ExtCmd('CreateUiElement',name,xamlStr) end function this.RemoveUiElement(name) if not this.uiElements[name] then InfCore.Log('WARNING: RemoveElement: could not find uiElement '..name) return end ExtCmd('RemoveUiElement',name) end --Menu specific --tex TODO: IHExt currently uses line end as command end so cant wrap whole lot in [[]] --but this way defeats the purpose of being able to just copy over xaml from vs local menuLineXaml={ [[]], } local inputLineXaml={ [[]], } local menuLine='menuLine' local menuItems='menuItems' local menuSetting='menuSetting' local inputElementName='inputLine' local UiElementVisible='UiElementVisible' function this.ShowMenu() --CULL this.CreateUiElement(menuLine,table.concat(menuLineXaml)) --CULL this.CreateUiElement(inputLine,table.concat(inputLineXaml)) ExtCmd(UiElementVisible,'menuWrap',1) ExtCmd(UiElementVisible,'menuItems',1) ExtCmd(UiElementVisible,'menuTitle',1) ExtCmd(UiElementVisible,'menuHelp',ivars.menu_enableHelp) end function this.HideMenu() ExtCmd(UiElementVisible,'menuWrap',0) ExtCmd(UiElementVisible,'menuItems',0) ExtCmd(UiElementVisible,'menuTitle',0) ExtCmd(UiElementVisible,'menuHelp',0) end function this.SetMenuLine(fullText,text) if this.debugModule then InfCore.Log("InfMgsvToExt.SetMenuLine:") end --CULL ExtCmd('SetContent',menuElementName,text) ExtCmd('SetTextBox',menuLine,text) ExtCmd('UpdateTable',menuItems,InfMenu.currentIndex-1,fullText) ExtCmd('SelectItem',menuItems,InfMenu.currentIndex-1) --tex setting the combo settings only needs to be run on selection of setting, selectcombo still needs to be run though local currentOption=InfMenu.GetCurrentOption() if currentOption then if currentOption.optionType=="OPTION" then local currentSetting=ivars[currentOption.name] -- InfCore.Log("currentOption:"..currentOption.name.." ivar="..tostring(currentSetting)) -- if currentOption.OnSelect then -- currentOption:OnSelect(ivars[currentOption.name]) -- end local settingNames=currentOption.settingNames if type(currentOption.GetSettingText)=="function" then ExtCmd('SelectCombo',menuSetting,currentSetting) elseif settingNames then ExtCmd('SelectCombo',menuSetting,currentSetting) elseif currentOption.settings then ExtCmd('SelectCombo',menuSetting,currentSetting) elseif currentSetting then--tex just a straight value ExtCmd('ClearCombo',menuSetting) ExtCmd('AddToCombo',menuSetting,currentSetting) ExtCmd('SelectCombo',menuSetting,0) end else--currentOption.optionType InfCore.ExtCmd('ClearCombo','menuSetting') end--currentOption.optionType end--if currentOption end--SetMenuLine function this.TakeFocus() ExtCmd('TakeFocus') InfCore.WriteToExtTxt() end return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfTppUtil.lua` ```lua --InfTppUtil.lua -- utility function dumping ground -- stuff that doesnt have any dependancies to tpp can go in InfUtil local this={} local NULL_ID=GameObject.NULL_ID local GetGameObjectId=GameObject.GetGameObjectId local GetTypeIndex=GameObject.GetTypeIndex --tex DEBUGNOW is this the right spot for this, do I need a InfDefine? this.MAX_OBJECTS_PER_TYPE=512--LIMIT tex see IHTearDown TearDownGameObjectTypes notes --LIMIT --SYNC soldier/any object max instance/totalCount --tex maxInstanceCount seems to be totalCount for gameobject in fox2 --see this.RunOnAllObjects --KLUDGE, would be better if we could grab it from exe this.maxInstanceCount={ --tex ih bumped totalCount in free roam missions: f300n0_npc.fox2 TppSoldier2={ [30010]=330, [30020]=330, [30050]=280, }, }--maxInstanceCount --tex adds game object names to another table, with a not NULL_ID check function this.ResetObjectPool(objectType,objectNames) local pool={} for i=1,#objectNames do local objectName=objectNames[i] local gameId=GetGameObjectId(objectType,objectName) if gameId==NULL_ID then InfCore.Log("ResetObjectPool: "..objectName.."==NULL_ID")--DEBUG else pool[#pool+1]=objectName end end return pool end --tex just from Tpp.IsGameObjectType, don't want to change it from local function this.IsGameObjectType(gameObjectId,checkType) if gameObjectId==nil then return end if gameObjectId==NULL_ID then return end local typeIndex=GetTypeIndex(gameObjectId) if typeIndex==checkType then return true else return false end end --DEBUG --tex loops at 512 -- local count=1100 -- local objectType="TppSoldier2" -- for index=0,count-1 do -- local gameId=GameObject.GetGameObjectIdByIndex(objectType,index) -- --tex GetGameObjectIdByIndex errors on index > instance count: 'instance index range error. index n is larger than maxInstanceCount n.' -- if gameId==NULL_ID then -- --tex shouldnt happen -- InfCore.Log("object index "..index.." ==NULL_ID") -- else -- local name=InfLookup.ObjectNameForGameId(gameId) -- InfCore.Log("Soldier index "..index..": gameId:"..gameId.." name:"..tostring(name)) -- --tex return an 'instance index range error. index is larger than maxInstanceCount .' -- --which makes little sense since the gameId is the same/is not the index. -- --errors/non errors seem to loop too (getting out of sync with the actual inputted index) -- --ex: ---- |765.58408987113|Soldier index 842: gameId:1354 name:nil << tex fine even though index is over instance count ---- |765.58408987113|ERROR:C:/GamesSD/MGS_TPP/mod/InfMenuCommands.lua:770: instance index range error. index 330 is larger than maxInstanceCount 330. << not even reporting correct index ---- |765.58408987113|Soldier index 843: gameId:1355 name:nil -- some other commands seem to be less safe and will hang or crash the game past max index -- so will have to either be dead accurate with the max index you supply, reply on GetPosition as a canary/break on error for those gameobjects that support it -- local pos=InfCore.PCall(function()GameObject.SendCommand(gameId,{id="GetPosition"})end) -- end -- end --GOTCHA: there's some funkyness to GetGameObjectIdByIndex. --GetMaxInstanceCount only works on a couple of gameobjects, hostages and walker gears (more frustrating kjp inconsistancy) --GetGameObjectIdByIndex itself doesn't seem to care about inputting index > instance count, it will loop the returned gameobject eturning the same gameobjectids --but running GameObject.SendCommand on a gameid from an index > instance count will return an 'instance index range error. index is larger than maxInstanceCount .' --which doesn't really make sense, it suggests that the gameid has the index, even though on inspection it seems exactly the same/repeated < index gameid --and it's only for some commands, others will hang or crash the game (again, this wouldn't be an issue if GetGameObjectIdByIndex didn't have a (seeminly) unsafe implmentation. function this.RunOnAllObjects(objectType,instanceCount,RunFunc) if instanceCount and instanceCount>this.MAX_OBJECTS_PER_TYPE then InfCore.Log("WARNING: InfTppUtil.RunOnAllObjects: instanceCount:"..tostring(instanceCount).." > MAX_OBJECTS_PER_TYPE:"..this.MAX_OBJECTS_PER_TYPE) end local count=GameObject.SendCommand({type=objectType},{id="GetMaxInstanceCount"}) InfCore.Log(objectType.." maxInstanceCount:"..tostring(count))--DEBUG if count==nil then count=instanceCount or this.maxInstanceCount[objectType] and this.maxInstanceCount[objectType][vars.missionCode] or this.MAX_OBJECTS_PER_TYPE end if count>0 then for index=0,count-1 do local gameId=GameObject.GetGameObjectIdByIndex(objectType,index) --tex GetGameObjectIdByIndex errors on index > instance count: 'instance index range error. index n is larger than maxInstanceCount n.' --RETAILBUG: even though GetGameObjectIdByIndex says 'larger than' the test seems to be >= since if you give it maxInstace it will still complain. --ex 'instance index range error. index 330 is larger than maxInstanceCount 330' if gameId==NULL_ID then --tex shouldnt happen InfCore.Log("object index "..index.." ==NULL_ID") else local success,ret=pcall(RunFunc,gameId,index) if not success then InfCore.Log("RunOnAllObjects GetGameObjectIdByIndex > instance count at index "..index..", breaking") break end end--if gameId end--for count end--if count end--RunOnAllObjects --tex general warp function for gameobjects that have warp/position commands --gameObjectId: object that you want to warp to pos, 0 for player --pos: {x,y,z,rotY} --rotY: if nil will try pos[4] --TODO test hostage --TODO verify rotY needs to be degrees or rads for each. is rads for vehicle, player? --is radians for GAME_OBJECT_TYPE_BOSSQUIET2 function this.Warp(gameId,pos,rotY) rotY=rotY or pos[4] or 0 local typeIndex=GameObject.GetTypeIndex(gameId) if typeIndex==TppGameObject.GAME_OBJECT_TYPE_PLAYER2 then --tex TppPlayer.Warp only allows #pos==3 (so no tacked on rotY) rotY=foxmath.NormalizeRadian(foxmath.DegreeToRadian(rotY)) local command={id="WarpAndWaitBlock",pos=pos,rotY=rotY} GameObject.SendCommand(gameId,command) elseif typeIndex==TppGameObject.GAME_OBJECT_TYPE_VEHICLE then pos=Vector3(pos[1],pos[2],pos[3]) rotY=foxmath.NormalizeRadian(foxmath.DegreeToRadian(rotY)) GameObject.SendCommand(gameId,{id="SetPosition",position=pos,rotY=rotY}) elseif typeIndex==TppGameObject.GAME_OBJECT_TYPE_WALKERGEAR2 or typeIndex==TppGameObject.GAME_OBJECT_TYPE_COMMON_WALKERGEAR2 then--tex buddy walker or enemy walker rotY=foxmath.NormalizeRadian(foxmath.DegreeToRadian(rotY)) GameObject.SendCommand(gameId,{id="SetPosition",pos=pos,rotY=rotY}) elseif TppDefine.HOSTAGE_GM_TYPE[typeIndex] or typeIndex==TppGameObject.GAME_OBJECT_TYPE_HOSTAGE_KAZ or typeIndex==TppGameObject.GAME_OBJECT_TYPE_OCELOT2 or typeIndex==TppGameObject.GAME_OBJECT_TYPE_HUEY2 or typeIndex==TppGameObject.GAME_OBJECT_TYPE_CODE_TALKER2 or typeIndex==TppGameObject.GAME_OBJECT_TYPE_SKULL_FACE2 or typeIndex==TppGameObject.GAME_OBJECT_TYPE_MANTIS2 then pos=Vector3(pos[1],pos[2],pos[3]) GameObject.SendCommand(gameId,{id="Warp",degRotationY=rotY,position=pos}) elseif typeIndex==TppGameObject.GAME_OBJECT_TYPE_BOSSQUIET2 then GameObject.SendCommand(gameId,{id="WarpRequest",pos=pos,rotY=rotY}) elseif typeIndex==TppGameObject.GAME_OBJECT_TYPE_VOLGIN2 then GameObject.SendCommand(gameId,{id="Warp",position=Vector3(pos),rotationY=rotY}) else local typeName=InfLookup.TppGameObject.typeIndex[typeIndex] InfCore.Log("WARNING: InfTppUtil.Warp: does not support gameObject type:"..tostring(typeName),true) end--if typeIndex end--Warp function this.IsDemoPlaying() return DemoDaemon.IsDemoPaused() or DemoDaemon.IsDemoPlaying() or DemoDaemon.GetPlayingDemoId() end return this ``` ### `ih_kap:tpp/gamedir-ih/GameDir/mod/modules/InfUtil.lua` ```lua -- InfUtil.lua -- utility function dumping ground -- should not have any dependancies to Tpp, put that in InfTppUtil local this={} function this.ClearTable(_table) for k,v in pairs(_table)do _table[k]=nil end end function this.ClearArray(_table) for i=0, #_table do _table[i]=nil end end function this.MergeArray(array1,array2) for i=0,#array2 do array1[#array1+1]=array2[i] end end function this.TableKeysToArray(inputTable,outputTable) outputTable=outputTable or {} for k,v in pairs(inputTable)do outputTable[#outputTable+1]=k end--for inputTable return outputTable end--TableKeysToArray --https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating --array to iterate and remove from --fnKeep: Return true to keep the value, or false to discard it function this.ArrayRemove(array, fnKeep) local j, n = 1, #array; for i=1,n do if (fnKeep(array, i, j)) then -- Move i's kept value to j's position, if it's not already there. if (i ~= j) then array[j] = array[i]; array[i] = nil; end j = j + 1; -- Increment position of where we'll place the next kept value. else array[i] = nil; end end return array; end--ArrayRemove function this.IsTableEmpty(checkTable) return next(checkTable)==nil end --tex removes fillCount number of items from sourceList and adds to fillList function this.FillList(fillCount,sourceList,fillList) local addedItems={} while fillCount>0 and #sourceList>0 do local item=sourceList[#sourceList] if item then sourceList[#sourceList]=nil--pop fillList[#fillList+1]=item addedItems[#addedItems+1]=item fillCount=fillCount-1 end end return addedItems end function this.CopyList(sourceList) if #sourceList==0 then return {} end local newList={} for i=1,#sourceList do newList[i]=sourceList[i] end return newList end function this.CopyTable(orig) local CopyTable=this.CopyTable local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[CopyTable(orig_key)] = CopyTable(orig_value) end setmetatable(copy, CopyTable(getmetatable(orig))) else -- number, string, boolean, etc copy = orig end return copy end--CopyTable -- https://stackoverflow.com/a/1283608 function this.MergeTable(t1, t2) local MergeTable=this.MergeTable for k,v in pairs(t2) do if type(v) == "table" then if type(t1[k] or false) == "table" then MergeTable(t1[k] or {}, t2[k] or {}) else t1[k] = v end else t1[k] = v end end return t1 end -- adpted from lume.find here: https://github.com/rxi/lume/blob/master/lume.lua function this.FindInTable(t, value) local iter = nil if type(t) == "table" and t[1] ~= nil then iter = ipairs elseif type(t) == "table" then iter = pairs end for k, v in iter(t) do if v == value then return k end end return nil end function this.SwapEntry(sourceTable,sourceKey,destKey) local currentEntry=sourceTable[sourceKey] sourceTable[sourceKey]=sourceTable[destKey] sourceTable[destKey]=currentEntry end--SwapEntry -- Fisher-Yates shuffle, produces new table -- via: https://stackoverflow.com/a/68486276 function this.ShuffleArray(array) local s = {} for i = 1, #array do s[i] = array[i] end for i = #array, 2, -1 do local j = math.random(i) s[i], s[j] = s[j], s[i] end return s end --DEBUGNOW is destructive to input table TODO better function this.RandomizeArray(array) if array==nil then InfCore.Log("WARNING: RandomizeArray array==nil") return nil end if #array==0 then return array end local randomizedArray={} local size=#array for i=1,size do local index=math.random(#array) table.insert(randomizedArray,array[index]) table.remove(array,index) end return randomizedArray end--RandomizeArray function this.GetRandomPool(pool) --DEBUGNOW -- if pool==nil or #pool==0 then -- return -- end local rndIndex=math.random(#pool) local name=pool[rndIndex] table.remove(pool,rndIndex) return name end function this.GetRandomInList(list) if #list==0 then return nil end return list[math.random(#list)] end function this.FindInList(list,findValue) for i,value in ipairs(list)do if value==findValue then return i end end return false end function this.InsertUniqueInList(list,insertValue) local hasValue=false for i,value in ipairs(list)do if value==insertValue then hasValue=true break end end if not hasValue then table.insert(list,insertValue) end end function this.WriteLuaList(fileName,lineFormat,list) local header='local this={' local lineFormat=lineFormat or '"%s",' local footer='}\nreturn this' local writeStrings={} writeStrings[#writeStrings+1]=header for i,entry in ipairs(list)do writeStrings[#writeStrings+1]=string.format(lineFormat,entry) end writeStrings[#writeStrings+1]=footer local filePath=InfCore.paths.mod..fileName..".lua" InfCore.Log("InfUtil.WriteList: "..filePath) InfCore.WriteStringTable(filePath,writeStrings) end --tex object for returning a random item with no repeats till it's returned all items this.ShuffleBag={ currentItem=nil, currentPosition=-1, data={}, New=function(self,table) local newBag={} newBag.currentItem=nil newBag.currentPosition=-1 newBag.data={} setmetatable(newBag,self) self.__index=self if table then newBag:Fill(table) end return newBag end, Fill=function(self,table,amount) local tableTypeStr="table" for i=1,#table do local item=table[i] if type(item)==tableTypeStr then self:Add(item[1],item[2]) else self:Add(item,amount) end end end, Add=function(self,item,amount) local amount=amount or 1 for i=1,amount do self.data[#self.data+1]=item self.currentPosition=#self.data end end, Next=function(self) --run out, start again if self.currentPosition<2 then self.currentPosition=#self.data self.currentItem=self.data[1] return self.currentItem end --picks between start of array and currentposition, which decreases from end of array local pos=math.random(self.currentPosition) self.currentItem=self.data[pos] self.data[pos]=self.data[self.currentPosition] self.data[self.currentPosition]=self.currentItem self.currentPosition=self.currentPosition-1 return self.currentItem end, Count=function(self) return #self.data end, } --string --tex NMC from lua wiki function this.Split(self,sep) local sep = sep or " " local fields = {} local pattern = string.format("([^%s]+)", sep) self:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end --tex NMC from lua wiki --MoonSharp doesn't like gmatch with long strings function this.SplitAlt(str,delim,maxNb) -- Eliminate bad cases... if string.find(str,delim)==nil then return{str} end if maxNb==nil or maxNb<1 then maxNb=0--No limit end local result={} local pat="(.-)"..delim.."()" local nb=0 local lastPos for part,pos in string.gmatch(str,pat) do nb=nb+1 result[nb]=part lastPos=pos if nb==maxNb then break end end -- Handle the last field if nb~=maxNb then result[nb+1]=string.sub(str,lastPos) end return result end function this.FindLast(searchString,findString) --Set the third arg to false to allow pattern matching local found=searchString:reverse():find(findString:reverse(),nil,true) if found then return searchString:len()-findString:len()-found+2 else return found end end --tex ex returns "cm_f0_h2_v000_eye1.fv2" from "/Assets/tpp/fova/common_source/chara/cm_head/face/cm_f0_h2_v000_eye1.fv2" function this.GetFileName(path,stripExt) local lastPos=this.FindLast(path,"/") local fileName=string.sub(path,lastPos+1,#path) if stripExt then local lastPos=this.FindLast(fileName,[[.]]) fileName=string.sub(fileName,1,lastPos-1) end return fileName end function this.GetFileExtension(path) local lastPos=this.FindLast(path,[[.]]) if lastPos then return string.sub(path,lastPos,string.len(path)) else return "" end end function this.StripExt(fileName) local lastPos=this.FindLast(fileName,[[.]]) return string.sub(fileName,1,lastPos-1) end --enumNames: array of enum names in enum order --enumTable: optional. will add enums to enumTable instead of returning new table. (so you can pass in same table as enumNames to add to that) function this.EnumFrom0(enumNames,enumTable) if type(enumNames)~="table"then return end enumTable=enumTable or {} for i=1,#enumNames do enumTable[enumNames[i]]=i-1--NMC: lua tables indexed from 1, enums indexed from 0 end return enumTable end--EnumFrom0 --as above but enums start at 1 function this.EnumFrom1(enumNames,enumTable) if type(enumNames)~="table"then return end enumTable=enumTable or {} local enumTable={} for i=1,#enumNames do enumTable[enumNames[i]]=i end return enumTable end--EnumFrom1 function this.GenerateNameList(fmt,num,list) local list=list or {} for i=0,num-1 do local name=string.format(fmt,i) table.insert(list,name) end return list end --tex index 0 based function this.From3Dto1D(x,y,z,xMax,yMax) return (z*xMax*yMax)+(y*xMax)+x end--From3Dto1D --tex index 0 based function this.From1Dto3D(idx,xMax,yMax) local z=math.floor(idx/(xMax*yMax)) idx=idx-(z*xMax*yMax) local y=math.floor(idx/xMax) local x=math.floor(idx%xMax) return x,y,z end--From1Dto3D --tex index 0 based function this.From2Dto1D(x,y,xMax) return (y*xMax)+x end--From3Dto1D --{x,y,z} vectors function this.YawTowardsLookPos(pos,lookPos) local dirVec=Vector3(lookPos[1]-pos[1],0,lookPos[3]-pos[3]) dirVec=dirVec:Normalize() return TppMath.RadianToDegree(foxmath.Atan2(dirVec:GetX(),dirVec:GetZ())) end --lua 5.2s pack, could add to table.pack I guess --http://lua-users.org/wiki/VarargTheSecondClassCitizen --basically using {...} with any nil args means you cant iterate it --using this you have the number of args in .n --so you can iterate with: for 1,args.n : args[i] function this.pack2(...) return {n=select("#",...),...}--tex sure would be nice if you could reuse table mr lua. end --t = a .pack2'ed varargs --better to inline unpack where you can instead of using this, --since you can play with the range for stuff like pcall returns function this.unpack2(t) return unpack(t,1,t.n) end --tex kind InfLookup stuff local concat=table.concat --stringArgs: table to use for stringArgs (opt to avoid creating new) --though it still creates a table to capture args, not sure how select is at working with varargs function this.GetArgsString(stringArgs,...) local args={...} local argsN=select("#",...)--tex gets actual size including nils for i=1,(argsN) do stringArgs[i]=tostring(args[i])--tex concat doesnt like nil, so need to pack into another table, but at least we can reuse that one end return concat(stringArgs,",",1,argsN) end--GetArgsString --packedReturns: pack2'ed pcall returns - so [1]==pcall success,[2+]==actual returns function this.GetPCallReturnsStrings(returnStrings,packedReturns) for i=1,packedReturns.n do returnStrings[i]=tostring(packedReturns[i]) end return concat(returnStrings,",",2,packedReturns.n)--tex 2 skip success in returns[1] end--GetPCallReturnsStrings function this.GetIndexFrom1(array) if array[0]~=nil then return -1 end return 0 end--GetIndexFrom1 --tex only lua from 1 arrays function this.SetArrayPos(toArray,xOrPos,Y,Z) if type(xOrPos)=='table'then Z=xOrPos[3] Y=xOrPos[2] xOrPos=xOrPos[1] end toArray[1]=xOrPos toArray[2]=Y toArray[3]=Z end--SetArrayPos --tex handles 0 or 1 based vec arrays --works on scriptvars since its using indexing --OUT:toArray function this.SetAnyArrayPos(toArray,xOrPos,Y,Z) local x,y,z=xOrPos,Y,Z--tex avoid unnessesary new table if type(xOrPos)=='table'then local fromIndexShift=this.GetIndexFrom1(xOrPos) x=xOrPos[1+fromIndexShift] y=xOrPos[2+fromIndexShift] z=xOrPos[3+fromIndexShift] end local toIndexShift=this.GetIndexFrom1(toArray) toArray[1+toIndexShift]=x toArray[2+toIndexShift]=y toArray[3+toIndexShift]=z end--SetArrayPos function this.PointOnCircle(origin,radius,angle) local x=origin[1]+radius*math.cos(math.rad(angle)) local y=origin[3]+radius*math.sin(math.rad(angle)) return {x,origin[2],y} end return this ``` ### `ih_kap:vscode.md` #### About the Infinite Heaven repo vscode setup For the Lua Language Server Extension by sumneko ##### \tpp\tpp.code-workspace: A vscode workspace for the dev (Infinite Heaven repo\tpp) folder. ##### tpp/vscode_hint-*: The vscode lua language server extension needs a bit of help making sense of mgsv/IHs setup, these lua files are parsed by the extension to map the files to their global modules (not used by IH mod itself). As the IH repo includes all the base game lua files in data1_dat-lua and fpkd-combined-lua this will let the lua extensions popup and go to definitions work. See the header in the files, or Infinite Heaven repo\vscode.md for more info. IH > Debug menu > Vscode Menu: "Commands to generate files to support using vscode with a mgsv project." debug_WriteVscodeHintOnTitle="Runs WriteVscodeHint when loading has reached Title" WriteVscodeHint - "Writes GameDir/mod/vscode/vscode_hint-mod.lua,vscode_hint-base_game.lua , writes a hint files for vscode lua language server extension of mod lua files loaded." debug_WriteVarsOnSave - "Runs WriteVars on IH save (which includes close IH menu)" WriteVars - "Writes vars, svars, gvars, mvars to GameDir/mod/vscode/, for manual perusal or use with vscode" ##### Mock Modules: The MockFox project https://github.com/TinManTex/MockFox Has mock modules for the lua modules defined by the exe: `MockFox repo\Generated\tpp\mockModulesAsGlobal` To use them with the vscode lua extension Download the MockFox repo and add those two folders in vscode Settings > Lua > Workspace: Libraries Though this will give the extension a heavier workload, and chew on a lot of ram, it will squelch a bunch of warnings and let you jump to the module definition to look up the enums it provides for example. ## Verified reference (local) — schemas copied from real IH source ### `reference_local:lua-input.md` #### Lua input: hotkeys via InfButton **Confidence:** VERIFIED — function names and constants read from `ih_kap:.../mod/modules/InfButton.lua`; the AnnounceLogView split confirmed against vanilla `TppUI.lua`. Fills the gap two testers hit: no verified hotkey path. ##### The input functions (real names) There is **no `IsButtonDown`**. Some docs say there is; the source has these four and only these: ```lua InfButton.OnButtonDown(mask) -- fired the frame the button goes down (edge) InfButton.OnButtonUp(mask) -- fired the frame it releases InfButton.OnButtonRepeat(mask) -- auto-repeat while held InfButton.OnButtonHoldTime(mask) -- how long it's been held ``` `mask` is `InfButton.`. Use `OnButtonDown` for a normal keypress trigger. Call these from your module's `Update`, gated cheaply: ```lua function this.Update(...) local InfButton = InfButton if not InfButton then return end local mask = InfButton.PLACE_MARKER if mask and InfButton.OnButtonDown(mask) then this.DoThing() end end ``` ##### GOTCHA: game buttons, not raw keys InfButton masks against `PlayerVars.scannedButtonsDirect` — these are *game actions*, not keyboard scancodes. "Press a key" means "press whatever key is bound to this game action". The `fox` module has `KEY_*` constants but the live dump exposes no key-state getter, so there is no verified raw-keyboard path from Lua. ##### The button constants ``` ACTION BINOCULARS BUTTON_PLACE_MARKER CALL CARRY CQC CQC_KNIFE_KILL DASH DECIDE DIVE DOWN ESCAPE EVADE FIRE HOLD INTERROGATE JUMP LEFT LIGHT_SWITCH MB_DEVICE MOVE_ACTION PLACE_MARKER PRIMARY_WEAPON READY_WEAPON RELOAD RIDE_OFF RIDE_ON RIGHT SECONDARY_WEAPON SIDE_ROLL SKILL STANCE STICK_L STICK_R STOCK SUBJECT TRIGGER_ACCEL TRIGGER_BREAK TRIGGER_L TRIGGER_R UP VEHICLE_CALL VEHICLE_CHANGE_SIGHT VEHICLE_DASH VEHICLE_FIRE VEHICLE_LIGHT_SWITCH VEHICLE_TOGGLE_WEAPON WALK ZOOM_CHANGE ``` (Plus several UNKNOWN7–13 placeholders, omitted.) ##### On-screen text: two different functions Confirmed from vanilla `TppUI.lua`: - `TppUiCommand.AnnounceLogViewLangId(langId, p1, p2, ...)` — for a **registered lang id**. This is what vanilla uses. - `TppUiCommand.AnnounceLogView(string)` — takes a **string**. Because a separate `...LangId` function exists for ids, plain `AnnounceLogView` is the free-text path. An unregistered lang id shows as its raw string anyway, so free text is safe. Always also write to `InfCore.Log` so you have output even if the on-screen call is finicky. ##### Player position vars `vars.playerPosX / playerPosY / playerPosZ` and `vars.playerRotY` — this is how IH itself reads player position (`InfMenuCommands.GetPlayerPos`). ## MGSV Modding and Research Wiki ### `wiki:Infinite_Heaven.md` Infinite Heaven is a mod for Metal Gear Solid V: The Phantom Pain intended to extend gameplay through customizable settings and features. It has several hundred toggleable options ranging from Subsistence mode for all missions, replay side-ops, Mother base invasions with multiple attack helicopters, Skull attacks in Free roam, Free-cam, skip heli rides, customization of enemy and mother base gear, foot, heavy vehicle and heli patrols in free roam, and much more. The purpose of this article is to provide installation steps, notes about the mod, in addition to troubleshooting steps for errors encountered while installing or using the mod. {:.small} ##### Installation **Video version:** > The video above was made prior to [IHHook](https://www.nexusmods.com/metalgearsolidvtpp/mods/1226) being split off into a separate download. When installing Infinite Heaven via Snakebite make sure to also install IHHook or the GUI won't load. {:.important} **Text version:** The text guide also covers optionally manually backing up the game data archive files, along with some troubleshooting tips. ###### Preparation/insurance 1. End/Abort any mission back to ACC before upgrading Infinite Heaven, upgrading a save that's mid-mission is likely to cause issues. 2. Back up your save files for safety: ```  \userdata\\287700 ``` and ```  \userdata\\311340 ``` > The placeholder name `` means wherever the Steam program directory is. By default the path is `C:\Program Files\Steam`. 3. MGSV uses two save directories: - `311340` is Ground Zeros Steam id, but used by MGSV for save data even if GZ is not installed. - `287700` is TPP steam id, most of the files here seem to be just backup, except for `TPP_GRAPHICS_CONFIG` 4. Copy both directories to a safe place. 5. You may want to back up periodically as you play in case you come across save breaking issues with the mod. ###### Backing up the games's data archives (vanilla copies) Snakebite should do this, but it's good to have your own backups. - Back up TPPs original `00.dat` and `01.dat` in ```  \steamapps\common\MGS_TPP\master\0\ ``` > This does not apply if you've already modded it. See below. ###### Backing up the games's data archives (modded copies) Either find and restore your original game `00.dat`/`01.dat` backup or revalidate the game through steam: - Right click on the game in steam library, choose properties from bottom of menu, local files tab, verify integrity of game cache button.
- Or paste this to File Explorer or your browser []() > If it gets stuck at 0% for more than a few minutes Steam is being stupid, validate one of the Valve games first, Half Life 2, Portal, etc, then try validating MGSV again. > > It should say 1 or two files need to be redownloaded, so go to steam downloads and make sure that is happening. {:.note} ###### Downloading Infinite Heaven and IHHook 1. Download the latest version of Infinite Heaven from the [Nexus Mods page](https://www.nexusmods.com/metalgearsolidvtpp/mods/45?tab=files). 2. Also download [IHHook](https://www.nexusmods.com/metalgearsolidvtpp/mods/1226?tab=files), which is required for Infinite Heaven's GUI. > Older versions of Infinite Heaven bundled IHHook with it but they've been split to separate downloads for some time now. ###### Installing SnakeBite 1. Get the latest SnakeBite Mod Manager from the [Nexus Mods page](http://nexusmods.com/metalgearsolidvtpp/mods/106/) and run through its setup. 2. Click on the Mods menu item on Snakebites main screen. 3. Uninstall any earlier version of Infinite Heaven from the Installed Mods tab. 4. Extract the Infinite Heaven zip files somewhere. 5. Click *Install .MGSV* on the Installed Mods tab in Snakebite 6. Browse to the `Infinite Heaven.msgv` from the Infinite Heaven zip file. > This step should only take a minute or so if no other mods installed (longer with other mods installed, basically proportional to the size of `00.dat`). See `log.txt` in the Snakebite directory which is updated with the install progress. {:start="7"} 7. Finally install [IHHook](/IHHook) by repeating step 5 then browsing to IHHook's `.mgsv` file. > **Troubleshooting:** if IH takes an excessively long time to install refer to the sticky comment on the Snakebite [Posts tab](https://www.nexusmods.com/metalgearsolidvtpp/mods/106?tab=posts) for possible fixes. {:.important} --- > **Troubleshooting:** if experiencing issues refer to the `Troubleshooting.txt` found in the `docs` directory of the Infinite Heaven zip you downloaded, or the [Troubleshooting section](#troubleshooting) further below on this page. {:.tip} > **Online/multiplayer:** all settings are reset to off on doing a FOB mission. It's recommended to play offline while the mod is installed. Snakebite mod manager allows easy toggling of mods. {:.note} --- ##### Features and Options All options in Infinite Heaven begin set with the game defaults and can be adjusted in the Infinite Heaven menus. For the full and up-to-date documentation of features, options and notes check out the included `Features and Options.txt` within the `docs` directory of the Infinite Heaven zip. Also check out TinManTex's [Youtube playlist](https://www.youtube.com/playlist?list=PLSKlVTXYh6F_fmq0u9UmN2XTnfdfcHKJF) where they demonstrate many of the features and options of Infinite Heaven. > If Infinite Heaven was not installed correctly the in-game menu will not appear while in the ACC. {:.note} --- ##### Using alongside other mods Infinite Heaven modifies a lot of the core game lua scripts, combining with mods that have their own versions of those files will break things in either obvious or subtle ways. Mods that shouldn't have a conflict with Infinite Heaven are: Model swaps, data table mods like development unlocks or times. Ask the mod author for a Snakebite install package. You can also convert many mods to Snakebite yourself either by trying the *Install .ZIP* option in the Installed Mods tab of Snakebite, or by using Makebite (comes with Snakebite) to create a Snakebite package (`.mgsv`). See the video below for a tutorial. > If you want to manually check to see if a mod conflicts unzip the Infinite Heaven `.mgsv` file with a zip tool of your choice and check to see if any of the files match files in the other mod. {:.tip} --- ##### Uninstallation 1. Exit any missions, return to the ACC. > The mod saves some varables to the save file, but on initial testing (feedback is welcome on this) there is no issue with loading a save from this mod after the mod has been removed (provided you have exited to ACC). 2. Use uninstall in SnakeBite. 3. Delete the `MGS_TPP\mod` directory. --- ##### Troubleshooting This section covers a range of steps to identify issues, provided by the developer, TinManTex. - Check the `FAQ Known Issues.txt` file in the `docs` directory of the Infinite Heaven zip first. - If you have issues with Snakebite try the suggestions in the stickied post on the Snakebite [Nexusmods Posts tab](https://www.nexusmods.com/metalgearsolidvtpp/mods/106?tab=posts). For other issues can you please provide the following files to help me test: - Your save game that's at the point it's having issues. The save files are located in: ```  \userdata\\311340\remote ``` > The default install path of Steam is: `C:\Program Files\Steam`. Usually the `TPP_GAME_DATA` file alone is sufficient. - Some files from the `MGS_TPP` game directory (usually in `Steam\SteamApps\common`): - `Snakebite.xml` from the `MGS_TPP` directory. - The entire `MGS_TPP\mod` directory (as this contains the `ih_save` as well as debug logs). > If you need a place to upload [mega.co.nz](https://mega.co.nz) works well, as does Dropbox. You can message me the link if you wish. {:.tip} --- **Error message: `Infinite Heaven: Could not load modules from MGS_TPP\mod\`** If you get this message shortly after startup make sure your game directory is actually named `MGS_TPP`. Then the following can be tried: - Running `mgsvtpp.exe` as Administrator. - Copying the `mod` directory to `C:\mod`, after running the game again open `ih_log.txt` from the mod directory and messaging me the log. After you have done so there's a couple of things you can try: - **Abort to ACC from title**: - At the title screen hold down the Escape key for a couple of seconds, the Fox logo will flash up breifly, upon selecting continue the game will load into ACC instead of the mission the save was on. - **Clear IH settings completely**: - Exit the game and delete `MGS_TPP\mod\saves\ih_save.lua` - **Resetting from scratch**: 1. Pressing the *Restore original game files* button in Snakebite's settings page. 2. Verifying game cache through the properties for the game in Steam. 3. Delete the entire `MGS_TPP\mod` directory (you may want to copy off the `ih_save.lua` in `MGS_TPP\mod\saves` first). At this point MGSV will be considered unmodded, but verify by loading the game. - **Re-run Snakebite and go through it's steps**. - Again test to see if the game loads. - **Install Infinite Heaven only.** - Test again to see if the issue still occurs. > If the game save is in-mission and not in ACC you may need to Abort to ACC from title (see above). {:.note} --- ###### Troubleshooting table | Problem | Technical details | Solution | | ------------------------------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Cannot install mod with Snakebite | ![](/assets/IFXUHRX.jpg) | This may be a problem with the Snakebite installation/User not backing up files. The easiest solution is to back up your saves, uninstall Snakebite, Uninstall MGSV and delete all data from `MGS_TPP`. Reinstall the game and make sure to back up all game files. | | Cannot uninstall or install mods | ![](/assets/111pture.PNG) | MGSV Still running in the background. Force kill with task manager or restart computer. | | IHExt overlay won't work | | Set `IHExt.exe` (located in `MGS_TPP/mod/`) to always run in administrator. | | Player model turning invisible - unable to move | This may not be an issue with IH. This could be a conflict between fova mods | Return to ACC, shut down the game, and check for mod conflicts with Snakebite. | | Repeating cutscenes when returning to MB | MB Cutscenes mode set to: Default or Play Selected | Disable MB cutscenes. | | Driving between clusters on MB results in freeze/crash | Conflict between mods and MB settings in IH | Minimize mod conflicts and reset IH settings. | | Emblem Keeps Resetting | This is caused by booting the game offline with IH settings | Disable this option and play Online, emblem will update. |