serverdata_overrides.php

main 181 lines · 5.2 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2
3/**
4 * Single-record edits layered on top of the bulk-uploaded server data handled
5 * by serverdata.php (config.lua / items.xml / monster files). Overrides live
6 * in their own table instead of being written into the parsed cache blob, so
7 * re-uploading a source file (serverdata_rebuild()/serverdata_store_upload())
8 * never discards a manual correction - serverdata_apply_overrides() merges
9 * this table over the parsed array every time serverdata_load() is called.
10 */
11
12function serverdata_override_sources(): array
13{
14 return array('config', 'items', 'creatures');
15}
16
17function serverdata_override_table_exists(): bool
18{
19 return znote_table_exists('znote_serverdata_overrides');
20}
21
22/** record_key => ['data' => array, 'deleted' => bool] for one source. */
23function serverdata_override_all(string $source): array
24{
25 if (!serverdata_override_table_exists()) {
26 return array();
27 }
28
29 $rows = db()->fetchAll(
30 "SELECT `record_key`, `data`, `deleted` FROM `znote_serverdata_overrides` WHERE `source` = ? ORDER BY `record_key` ASC;",
31 array($source)
32 );
33
34 $out = array();
35 if (is_array($rows)) {
36 foreach ($rows as $row) {
37 $decoded = json_decode((string)($row['data'] ?? ''), true);
38 $out[(string)$row['record_key']] = array(
39 'data' => is_array($decoded) ? $decoded : array(),
40 'deleted' => !empty($row['deleted']),
41 );
42 }
43 }
44
45 return $out;
46}
47
48function serverdata_override_get(string $source, string $key)
49{
50 $all = serverdata_override_all($source);
51
52 return $all[$key] ?? null;
53}
54
55/** Edit an existing record or add a brand-new one. Also un-deletes a previously removed key. */
56function serverdata_override_set(string $source, string $key, array $data, string $updatedBy = ''): bool
57{
58 if (!serverdata_override_table_exists() || !in_array($source, serverdata_override_sources(), true) || $key === '') {
59 return false;
60 }
61
62 $json = (string)json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
63
64 return db()->execute("
65 INSERT INTO `znote_serverdata_overrides` (`source`, `record_key`, `data`, `deleted`, `updated_by`, `updated_at`)
66 VALUES (?, ?, ?, 0, ?, ?)
67 ON DUPLICATE KEY UPDATE `data` = VALUES(`data`), `deleted` = 0, `updated_by` = VALUES(`updated_by`), `updated_at` = VALUES(`updated_at`);
68 ", array($source, $key, $json, $updatedBy, time()));
69}
70
71/**
72 * Tombstones a record instead of deleting the row, so it keeps winning over
73 * whatever the base cache still has for that key (e.g. it came from the
74 * uploaded XML too). serverdata_override_set() on the same key clears the
75 * tombstone again.
76 */
77function serverdata_override_delete(string $source, string $key, string $updatedBy = ''): bool
78{
79 if (!serverdata_override_table_exists() || $key === '') {
80 return false;
81 }
82
83 return db()->execute("
84 INSERT INTO `znote_serverdata_overrides` (`source`, `record_key`, `data`, `deleted`, `updated_by`, `updated_at`)
85 VALUES (?, ?, '{}', 1, ?, ?)
86 ON DUPLICATE KEY UPDATE `deleted` = 1, `updated_by` = VALUES(`updated_by`), `updated_at` = VALUES(`updated_at`);
87 ", array($source, $key, $updatedBy, time()));
88}
89
90function serverdata_apply_overrides_config(array $baseData, array $overrides): array
91{
92 foreach ($overrides as $key => $row) {
93 if ($row['deleted'] || !array_key_exists('value', $row['data'])) {
94 continue;
95 }
96 $baseData[$key] = $row['data']['value'];
97 }
98 return $baseData;
99}
100
101function serverdata_apply_overrides_creatures(array $baseData, array $overrides): array
102{
103 $byName = array();
104 foreach ($baseData as $i => $row) {
105 if (isset($row['name'])) {
106 $byName[$row['name']] = $i;
107 }
108 }
109
110 foreach ($overrides as $key => $row) {
111 if ($row['deleted']) {
112 if (isset($byName[$key])) {
113 unset($baseData[$byName[$key]]);
114 }
115 continue;
116 }
117
118 $record = $row['data'];
119 $record['name'] = $key;
120
121 if (isset($byName[$key])) {
122 $baseData[$byName[$key]] = $record;
123 } else {
124 $baseData[] = $record;
125 }
126 }
127
128 $baseData = array_values($baseData);
129 usort($baseData, static function (array $a, array $b): int {
130 return strcasecmp((string)($a['name'] ?? ''), (string)($b['name'] ?? ''));
131 });
132
133 return $baseData;
134}
135
136function serverdata_apply_overrides_items(array $baseData, array $overrides): array
137{
138 foreach ($overrides as $key => $row) {
139 $parts = explode(':', $key, 2);
140 if (count($parts) !== 2) {
141 continue;
142 }
143 [$type, $id] = $parts;
144
145 if ($row['deleted']) {
146 unset($baseData[$type][$id]);
147 continue;
148 }
149
150 $baseData[$type][$id] = $row['data'];
151 }
152 return $baseData;
153}
154
155/**
156 * Merges the overrides table over a parsed serverdata array. Called from
157 * serverdata_load() so every existing reader (items.php, creatures.php, the
158 * public serverinfo.php, monster_loot.php's derived cache, ...) sees edits
159 * for free.
160 */
161function serverdata_apply_overrides(string $source, $baseData)
162{
163 if (!in_array($source, serverdata_override_sources(), true)) {
164 return $baseData;
165 }
166
167 $overrides = serverdata_override_all($source);
168 if (!$overrides) {
169 return $baseData;
170 }
171
172 $baseData = is_array($baseData) ? $baseData : array();
173
174 switch ($source) {
175 case 'config': return serverdata_apply_overrides_config($baseData, $overrides);
176 case 'creatures': return serverdata_apply_overrides_creatures($baseData, $overrides);
177 case 'items': return serverdata_apply_overrides_items($baseData, $overrides);
178 default: return $baseData;
179 }
180}
181
Top