aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/mods/reload/reloadmodweapons.cpp
blob: 077c283517607ce002fcdf1dddff48a1cbac7189 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include "mods/modmanager.h"
#include "dedicated/dedicated.h"
#include "engine/r2engine.h"

AUTOHOOK_INIT()

OFFSET_STRUCT(WeaponDefinition)
{
	FIELD(5, bool bReloadScriptFuncs);
	FIELD(6, char pWeaponName[]); // this probably has a max length but i do not know what it is
};

OFFSET_STRUCT(GlobalWeaponDefs)
{
	// each entry is 24 bytes, but no clue what the bytes after the def are, so just ignore atm
	// need the full struct so we iterate properly
	OFFSET_STRUCT(WeaponDefContainer)
	{
		STRUCT_SIZE(24);
		WeaponDefinition* pWeaponDef;
	};

	FIELD(16, WeaponDefContainer m_Weapons[]);
};

VAR_AT(client.dll + 0xB33A02, uint16_t*, g_pnClientWeaponsLoaded);
VAR_AT(client.dll + 0xB339E8, GlobalWeaponDefs**, g_ppClientWeaponDefs);
VAR_AT(server.dll + 0xBB57C2, uint16_t*, g_pnServerWeaponsLoaded);
VAR_AT(server.dll + 0xBB57A8, GlobalWeaponDefs**, g_ppServerWeaponDefs);

FUNCTION_AT(client.dll + 0x3D2FB0, void,, ClientReparseWeapon, (WeaponDefinition* pWeapon));
FUNCTION_AT(client.dll + 0x3CE270, void,, ClientReloadWeaponCallbacks, (int nWeaponIndex));
FUNCTION_AT(client.dll + 0x6D1170, void,, ServerReparseWeapon, (WeaponDefinition * pWeapon));
FUNCTION_AT(client.dll + 0x6CCE80, void, , ServerReloadWeaponCallbacks, (int nWeaponIndex));

std::unordered_set<std::string> setsClientWeaponsToReload;
std::unordered_set<std::string> setsServerWeaponsToReload;

// used for passing client/server funcs/data/pointers to TryReloadWeapon
struct SidedWeaponReloadPointers
{
	// data pointers
	uint16_t* m_pnWeaponsLoaded;
	GlobalWeaponDefs** m_ppWeaponDefs;

	std::unordered_set<std::string>* m_psetsWeaponsToReload;

	// funcs
	void (*m_fnReparseWeapon)(WeaponDefinition* pWeapon);
	void (*m_fnReloadWeaponCallbacks)(int nWeaponIndex);

	SidedWeaponReloadPointers() {} // don't use
	SidedWeaponReloadPointers(
		uint16_t* pnWeaponsLoaded,
		GlobalWeaponDefs** ppWeaponDefs,
		std::unordered_set<std::string>* psetsWeaponsToReload,
		void (*fnReparseWeapon)(WeaponDefinition*),
		void (*fnReloadWeaponCallbacks)(int))
	{
		m_pnWeaponsLoaded = pnWeaponsLoaded;
		m_ppWeaponDefs = ppWeaponDefs;
		m_psetsWeaponsToReload = psetsWeaponsToReload;
		m_fnReparseWeapon = fnReparseWeapon;
		m_fnReloadWeaponCallbacks = fnReloadWeaponCallbacks;
	}
} clientReloadPointers, serverReloadPointers;

int WeaponIndexByName(const char* pWeaponName, const SidedWeaponReloadPointers* pReloadPointers)
{
	for (int i = 0; i < *pReloadPointers->m_pnWeaponsLoaded; i++)
	{
		const WeaponDefinition* pWeapon = (*pReloadPointers->m_ppWeaponDefs)->m_Weapons[i].pWeaponDef;
		if (!strcmp(pWeapon->pWeaponName, pWeaponName))
			return i;
	}

	return -1;
}

void ModManager::DeferredReloadWeapons(const std::unordered_set<std::string> setsWeapons)
{
	// if there's still weapons that need reloading, then keep them, just reload the new stuff
	for (const std::string& sWeapon : setsWeapons)
	{
		setsClientWeaponsToReload.insert(sWeapon);
		setsServerWeaponsToReload.insert(sWeapon);
	}
}

void ModManager::TryImmediateReloadWeapons(const std::unordered_set<std::string> setsWeapons)
{
	// try to reload all weapons on client and server, if we can't reload for any reason
	// (either due to either side being inactive, or just failure to reload)
	for (const std::string& sWeapon : setsWeapons)
	{
		if (!IsDedicatedServer())
		{
			setsClientWeaponsToReload.insert(sWeapon);
			// if this fails, the weapon will still be there for deferred reload
			TryReloadWeapon(sWeapon.c_str(), &clientReloadPointers);
		}

		setsServerWeaponsToReload.insert(sWeapon);
		TryReloadWeapon(sWeapon.c_str(), &serverReloadPointers);
	}
}

bool ModManager::TryReloadWeapon(const char* pWeaponName, const SidedWeaponReloadPointers* pReloadPointers)
{
	if (!pReloadPointers->m_psetsWeaponsToReload->contains(pWeaponName))
		return false; // don't reload

	int nWeaponIndex = WeaponIndexByName(pWeaponName, pReloadPointers);
	if (nWeaponIndex == -1) // weapon isn't loaded at all, no need to reload!
		return false;

	spdlog::info("ModManager::TryReloadWeapon reloading weapon {}", pWeaponName);

	WeaponDefinition* pWeapon = (*pReloadPointers->m_ppWeaponDefs)->m_Weapons[nWeaponIndex].pWeaponDef;
	bool bReloadScriptFuncs = pWeapon->bReloadScriptFuncs; // this is reset after reparse
	pReloadPointers->m_fnReparseWeapon(pWeapon);
	if (bReloadScriptFuncs)
		pReloadPointers->m_fnReloadWeaponCallbacks(nWeaponIndex);

	pReloadPointers->m_psetsWeaponsToReload->erase(pWeaponName);
	return true;
}

// TODO: server implementation for this?
// clang-format off
AUTOHOOK(ClientPrecacheWeaponFromStringtable, client.dll + 0x195A60,
bool, __fastcall, (void* a1, void* a2, void* a3, const char* pWeaponName))
// clang-format on
{
	if (g_pModManager->TryReloadWeapon(pWeaponName, &clientReloadPointers))
		return true;

	spdlog::info("PrecacheWeaponFromStringtable: {}", pWeaponName);
	return ClientPrecacheWeaponFromStringtable(a1, a2, a3, pWeaponName);
}

ON_DLL_LOAD_CLIENT("client.dll", ModReloadWeaponsClient, (CModule module))
{
	AUTOHOOK_DISPATCH_MODULE(client.dll)

	clientReloadPointers = SidedWeaponReloadPointers(
		g_pnClientWeaponsLoaded, g_ppClientWeaponDefs, &setsClientWeaponsToReload, ClientReparseWeapon, ClientReloadWeaponCallbacks);
}

ON_DLL_LOAD_CLIENT("server.dll", ModReloadWeaponsServer, (CModule module))
{
	AUTOHOOK_DISPATCH_MODULE(server.dll)

	serverReloadPointers = SidedWeaponReloadPointers(
		g_pnServerWeaponsLoaded, g_ppServerWeaponDefs, &setsServerWeaponsToReload, ServerReparseWeapon, ServerReloadWeaponCallbacks);
}