summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTEXT.md9
-rw-r--r--src/esp.cpp41
-rw-r--r--src/hl2h.h1
-rw-r--r--src/server.cpp18
4 files changed, 54 insertions, 15 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 1d365e8..741050d 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -174,6 +174,15 @@ vocabulary / decisions.
174- Bugbait-controlled antlions are also treated as friendly when their server 174- Bugbait-controlled antlions are also treated as friendly when their server
175 `m_hFollowTarget` is set, covering antlions that spawned before the global 175 `m_hFollowTarget` is set, covering antlions that spawned before the global
176 `antlion_allied` state changed. 176 `antlion_allied` state changed.
177- Stock NPCs have no `m_Relationship` rows at all: their disposition comes from
178 the global `CHL2GameRules::InitDefaultRelationships()` table indexed by
179 `Classify()` (e.g. `CLASS_PLAYER_ALLY -> D_LI`, `CLASS_HEADCRAB -> D_HT`).
180 That table is not stored per entity, so `relation=-1` is normal and the
181 model heuristic is the fallback for those NPCs (Kleiner, Combine, ...).
182- `m_NPCState` (`FIELD_INTEGER`, server datamap) is read as well:
183 `NPC_STATE_SCRIPT` (4) marks scripted/passive actors as neutral, so they are
184 not aimbot targets and draw with the neutral colour. Lamarr
185 (`models/Lamarr.mdl`) is additionally listed as neutral by model.
177 186
178## Bones / ESP 187## Bones / ESP
179 188
diff --git a/src/esp.cpp b/src/esp.cpp
index 405dea7..50d7b5d 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -139,8 +139,10 @@ static bool model_is_friendly(const char *m) {
139 139
140// harmless wildlife that also happens to be CAI_BaseNPC 140// harmless wildlife that also happens to be CAI_BaseNPC
141static bool model_is_neutral(const char *m) { 141static bool model_is_neutral(const char *m) {
142 // Lamarr is Kleiner's pet headcrab: scripted and harmless, and the user
143 // wants her shown neutral rather than hostile.
142 static const char *patterns[] = {"crow", "gull", "pigeon", "seagull", 144 static const char *patterns[] = {"crow", "gull", "pigeon", "seagull",
143 "bird", nullptr}; 145 "bird", "Lamarr", nullptr};
144 if (!m) return false; 146 if (!m) return false;
145 for (int i = 0; patterns[i]; i++) 147 for (int i = 0; patterns[i]; i++)
146 if (strstr(m, patterns[i])) 148 if (strstr(m, patterns[i]))
@@ -155,42 +157,55 @@ static bool model_is_floor_turret(const char *m) {
155static bool npc_is_friendly(int idx, const char *model, int local_idx, 157static bool npc_is_friendly(int idx, const char *model, int local_idx,
156 bool &neutral) { 158 bool &neutral) {
157 int rel = server_npc_relation(idx, local_idx); 159 int rel = server_npc_relation(idx, local_idx);
160 int state = server_npc_state(idx);
158 int turret = -1; 161 int turret = -1;
159 // Citizen-built and Alyx-hacked turrets never add an m_Relationship row; 162 // Citizen-built and Alyx-hacked turrets never add an m_Relationship row;
160 // their Classify() (CLASS_PLAYER_ALLY) comes from server runtime state. 163 // their Classify() (CLASS_PLAYER_ALLY) comes from server runtime state.
161 if (model_is_floor_turret(model)) 164 if (model_is_floor_turret(model))
162 turret = server_npc_turret_flags(idx); 165 turret = server_npc_turret_flags(idx);
163 // Debug-only: relation values are the authoritative answer and are the 166 // Debug-only: relation/state changes are the authoritative signals and the
164 // first thing to check when a friendly NPC still looks hostile. Logged only 167 // first thing to check when a friendly NPC still looks hostile.
165 // when an entity's relation changes, so this does not flood the log.
166 static int rel_cache[2048]; 168 static int rel_cache[2048];
167 static bool rel_cache_init = false; 169 static int state_cache[2048];
168 if (!rel_cache_init) { 170 static bool cache_init = false;
171 if (!cache_init) {
169 for (int i = 0; i < 2048; i++) 172 for (int i = 0; i < 2048; i++)
170 rel_cache[i] = -999; 173 rel_cache[i] = state_cache[i] = -999;
171 rel_cache_init = true; 174 cache_init = true;
172 } 175 }
173 if (idx > 0 && idx < 2048 && rel_cache[idx] != rel) { 176 if (idx > 0 && idx < 2048 &&
177 (rel_cache[idx] != rel || state_cache[idx] != state)) {
174 rel_cache[idx] = rel; 178 rel_cache[idx] = rel;
175 logd("npc %d (%s): relation=%d followtarget=%d turret=%d local=%d", idx, 179 state_cache[idx] = state;
176 model ? model : "?", rel, 180 logd("npc %d (%s): relation=%d state=%d followtarget=%d turret=%d "
181 "local=%d", idx, model ? model : "?", rel, state,
177 (int)server_npc_has_follow_target(idx), turret, local_idx); 182 (int)server_npc_has_follow_target(idx), turret, local_idx);
178 } 183 }
179 if (rel == 3) { // D_LI 184 if (rel == 3) { // D_LI
180 neutral = false; 185 neutral = false;
181 return true; 186 return true;
182 } 187 }
188 if (turret > 0) { // citizen-built / Alyx-hacked
189 neutral = false;
190 return true;
191 }
183 if (rel == 4) { // D_NU 192 if (rel == 4) { // D_NU
184 neutral = true; 193 neutral = true;
185 return false; 194 return false;
186 } 195 }
196 // NPC_STATE_SCRIPT = 4: cutscene actors and scripted pets are passive even
197 // when their class relationship says hate (Lamarr, scripted Combine, ...).
198 if (state == 4) {
199 neutral = true;
200 return false;
201 }
187 if (rel == 1 || rel == 2) { // D_HT / D_FR 202 if (rel == 1 || rel == 2) { // D_HT / D_FR
188 neutral = false; 203 neutral = false;
189 return false; 204 return false;
190 } 205 }
191 if (turret >= 0) { 206 if (turret >= 0) { // ordinary floor turret
192 neutral = false; 207 neutral = false;
193 return turret != 0; 208 return false;
194 } 209 }
195 // Bugbait-controlled antlions follow a target even when their default 210 // Bugbait-controlled antlions follow a target even when their default
196 // relationship was never rewritten. 211 // relationship was never rewritten.
diff --git a/src/hl2h.h b/src/hl2h.h
index 6f23221..877afac 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -266,6 +266,7 @@ bool server_set_velocity_z(int idx, float vz);
266int server_active_weapon(int idx); 266int server_active_weapon(int idx);
267int server_npc_relation(int idx, int local_idx); 267int server_npc_relation(int idx, int local_idx);
268bool server_npc_has_follow_target(int idx); 268bool server_npc_has_follow_target(int idx);
269int server_npc_state(int idx);
269int server_npc_turret_flags(int idx); 270int server_npc_turret_flags(int idx);
270int server_model_index(void *base); 271int server_model_index(void *base);
271void find_modules(); 272void find_modules();
diff --git a/src/server.cpp b/src/server.cpp
index 19be144..c2864c8 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -50,6 +50,7 @@ static int g_srv_relationship = -1;
50static int g_srv_followtarget = -1; 50static int g_srv_followtarget = -1;
51static int g_srv_spawnflags = -1; 51static int g_srv_spawnflags = -1;
52static int g_srv_hacked = -1; 52static int g_srv_hacked = -1;
53static int g_srv_npcstate = -1;
53static bool g_srv_ready = false; 54static bool g_srv_ready = false;
54static bool g_srv_checked = false; 55static bool g_srv_checked = false;
55 56
@@ -139,15 +140,16 @@ bool resolve_server_netvars() {
139 // CBaseEntity::m_spawnflags is a DEFINE_KEYFIELD at a low offset. 140 // CBaseEntity::m_spawnflags is a DEFINE_KEYFIELD at a low offset.
140 g_srv_spawnflags = datamap_offset("m_spawnflags", FIELD_INTEGER, 0x4); 141 g_srv_spawnflags = datamap_offset("m_spawnflags", FIELD_INTEGER, 0x4);
141 g_srv_hacked = datamap_offset("m_bHackedByAlyx", FIELD_BOOLEAN); 142 g_srv_hacked = datamap_offset("m_bHackedByAlyx", FIELD_BOOLEAN);
143 g_srv_npcstate = datamap_offset("m_NPCState", FIELD_INTEGER);
142 g_srv_ready = g_srv_health > 0; 144 g_srv_ready = g_srv_health > 0;
143 if (g_srv_ready) { 145 if (g_srv_ready) {
144 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " 146 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d "
145 "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " 147 "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d "
146 "followtarget=%d spawnflags=%d hacked=%d", 148 "followtarget=%d spawnflags=%d hacked=%d npcstate=%d",
147 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, 149 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate,
148 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, 150 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity,
149 g_srv_activeweapon, g_srv_relationship, g_srv_followtarget, 151 g_srv_activeweapon, g_srv_relationship, g_srv_followtarget,
150 g_srv_spawnflags, g_srv_hacked); 152 g_srv_spawnflags, g_srv_hacked, g_srv_npcstate);
151 fail_logged = false; 153 fail_logged = false;
152 } else if (!fail_logged) { 154 } else if (!fail_logged) {
153 fail_logged = true; 155 fail_logged = true;
@@ -254,6 +256,18 @@ int server_npc_relation(int idx, int local_idx) {
254 return class_relation; 256 return class_relation;
255} 257}
256 258
259// NPC_STATE_SCRIPT = 4 (ai_npcstate.h). Scripted NPCs are passive: cutscene
260// actors, Kleiner's Lamarr, scripted soldiers standing down, and so on.
261int server_npc_state(int idx) {
262 if (!g_srv_ready || g_srv_npcstate < 0)
263 return -1;
264 void *base = nullptr;
265 server_entity_health(idx, nullptr, nullptr, &base);
266 if (!base)
267 return -1;
268 return *(int *)((uintptr_t)base + g_srv_npcstate);
269}
270
257bool server_npc_has_follow_target(int idx) { 271bool server_npc_has_follow_target(int idx) {
258 if (!g_srv_ready || g_srv_followtarget < 0) 272 if (!g_srv_ready || g_srv_followtarget < 0)
259 return false; 273 return false;