creatures_editor.php

main 197 lines · 8.1 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2/**
3 * Title: Creatures
4 * Icon: fa-paw
5 * Group: Server Info
6 * Order: 13
7 * Description: Add or edit a single monster without re-uploading the whole monster set.
8 * Hidden: true
9 */
10
11
12if (!defined('ACP_ROOT')) {
13 http_response_code(403);
14 die('Direct access denied.');
15}
16
17$creatures = serverdata_load('creatures');
18$creatures = is_array($creatures) ? $creatures : array();
19
20if (!$creatures && !serverdata_override_table_exists()) {
21 acp_flash_error(t_default('acp.crted.no_data', 'Upload your monster data on Server Info first - there is nothing published to edit yet.'));
22 acp_redirect('serverinfo');
23}
24
25if ($_SERVER['REQUEST_METHOD'] === 'POST') {
26 $adminName = (string)($GLOBALS['user_data']['name'] ?? '');
27 $action = (string)($_POST['crted_action'] ?? '');
28
29 if ($action === 'save') {
30 $originalName = trim((string)($_POST['original_name'] ?? ''));
31 $name = trim((string)($_POST['name'] ?? ''));
32
33 if ($name === '') {
34 acp_flash_error(t_default('acp.crted.missing_fields', 'A name is required.'));
35 acp_redirect('creatures_editor');
36 }
37
38 $record = array(
39 'health' => max(0, intv($_POST['health'] ?? 0)),
40 'experience' => max(0, intv($_POST['experience'] ?? 0)),
41 'speed' => max(0, intv($_POST['speed'] ?? 0)),
42 'race' => trim((string)($_POST['race'] ?? '')),
43 'looktype' => max(0, intv($_POST['looktype'] ?? 0)),
44 );
45
46 $ok = serverdata_override_set('creatures', $name, $record, $adminName);
47
48 // Renaming: the old key must stop winning over the base cache too.
49 if ($ok && $originalName !== '' && $originalName !== $name) {
50 serverdata_override_delete('creatures', $originalName, $adminName);
51 }
52
53 if ($ok) {
54 acp_log('serverdata.creature_save', $name);
55 acp_flash_success(t_default('acp.crted.saved', '{name} saved.', ['name' => $name]));
56 } else {
57 acp_flash_error(t_default('acp.crted.save_failed', 'Could not save - run the pending database migration first (Admin Panel > Migrations).'));
58 }
59 acp_redirect('creatures_editor');
60 }
61
62 if ($action === 'delete') {
63 $name = trim((string)($_POST['name'] ?? ''));
64 if (serverdata_override_delete('creatures', $name, $adminName)) {
65 acp_log('serverdata.creature_delete', $name);
66 acp_flash_success(t_default('acp.crted.removed', '{name} removed.', ['name' => $name]));
67 } else {
68 acp_flash_error(t_default('acp.crted.remove_failed', 'Could not remove that creature.'));
69 }
70 acp_redirect('creatures_editor');
71 }
72}
73
74$editName = trim((string)($_GET['edit'] ?? ''));
75$editing = null;
76if ($editName !== '') {
77 foreach ($creatures as $c) {
78 if (($c['name'] ?? '') === $editName) {
79 $editing = $c;
80 break;
81 }
82 }
83}
84
85$list = $creatures;
86usort($list, static function (array $a, array $b): int {
87 return strcasecmp((string)($a['name'] ?? ''), (string)($b['name'] ?? ''));
88});
89?>
90
91<section class="acp-card">
92 <header class="acp-card-head">
93 <h2><?= $editing ? h(t_default('acp.crted.edit_title', 'Edit {name}', ['name' => $editName])) : h(t_default('acp.crted.add_title', 'Add a creature')) ?></h2>
94 <p><?= h(t_default('acp.crted.sub', 'Saved separately from the uploaded monster files - a later re-upload will not overwrite this.')) ?></p>
95 </header>
96 <div class="acp-card-body">
97 <form method="post">
98 <?= acp_csrf_field() ?>
99 <input type="hidden" name="crted_action" value="save">
100 <input type="hidden" name="original_name" value="<?= h($editName) ?>">
101
102 <div class="acp-row">
103 <div class="acp-field">
104 <label class="acp-label" for="crted_name"><?= t_default('acp.crted.field_name', 'Name') ?></label>
105 <input class="acp-input" type="text" id="crted_name" name="name" value="<?= h((string)($editing['name'] ?? '')) ?>" required>
106 </div>
107 <div class="acp-field">
108 <label class="acp-label" for="crted_race"><?= t_default('acp.crted.field_race', 'Race') ?></label>
109 <input class="acp-input" type="text" id="crted_race" name="race" value="<?= h((string)($editing['race'] ?? '')) ?>" placeholder="blood, venom, undead...">
110 </div>
111 </div>
112
113 <div class="acp-row">
114 <div class="acp-field">
115 <label class="acp-label" for="crted_health"><?= t_default('acp.crted.field_health', 'Health') ?></label>
116 <input class="acp-input" type="number" min="0" id="crted_health" name="health" value="<?= (int)($editing['health'] ?? 0) ?>">
117 </div>
118 <div class="acp-field">
119 <label class="acp-label" for="crted_experience"><?= t_default('acp.crted.field_experience', 'Experience') ?></label>
120 <input class="acp-input" type="number" min="0" id="crted_experience" name="experience" value="<?= (int)($editing['experience'] ?? 0) ?>">
121 </div>
122 <div class="acp-field">
123 <label class="acp-label" for="crted_speed"><?= t_default('acp.crted.field_speed', 'Speed') ?></label>
124 <input class="acp-input" type="number" min="0" id="crted_speed" name="speed" value="<?= (int)($editing['speed'] ?? 0) ?>">
125 </div>
126 <div class="acp-field">
127 <label class="acp-label" for="crted_looktype"><?= t_default('acp.crted.field_looktype', 'Looktype') ?></label>
128 <input class="acp-input" type="number" min="0" id="crted_looktype" name="looktype" value="<?= (int)($editing['looktype'] ?? 0) ?>">
129 </div>
130 </div>
131
132 <div class="acp-actions">
133 <button class="acp-btn acp-btn--green" type="submit"><i class="fa fa-check"></i> <?= $editing ? t_default('acp.crted.save_btn', 'Save') : t_default('acp.crted.add_btn', 'Add creature') ?></button>
134 <?php if ($editing): ?>
135 <a class="acp-btn acp-btn--ghost" href="<?= h(acp_url('creatures_editor')) ?>"><?= t_default('acp.crted.cancel', 'Cancel edit') ?></a>
136 <?php endif; ?>
137 </div>
138 </form>
139 </div>
140</section>
141
142<section class="acp-card">
143 <header class="acp-card-head">
144 <h2><?= t_default('acp.crted.list_title', 'Published creatures') ?></h2>
145 </header>
146 <div class="acp-card-body is-flush">
147 <?php if ($list): ?>
148 <?php if (count($list) > 8): ?>
149 <div class="acp-toolbar">
150 <div style="display:flex;gap:8px;flex:1 1 320px;max-width:460px;">
151 <input class="acp-input" type="search" data-acp-search-input="crtedTable"
152 placeholder="<?= h(t_default('acp.crted.search_placeholder', 'Search creatures...')) ?>">
153 </div>
154 <span class="is-muted" data-acp-search-count="crtedTable"></span>
155 </div>
156 <?php endif; ?>
157 <div class="acp-table-wrap">
158 <table class="acp-table" data-sortable id="crtedTable">
159 <thead>
160 <tr>
161 <th><?= t_default('acp.crted.col_name', 'Name') ?></th>
162 <th class="is-num"><?= t_default('acp.crted.col_health', 'Health') ?></th>
163 <th class="is-num"><?= t_default('acp.crted.col_experience', 'Experience') ?></th>
164 <th><?= t_default('acp.crted.col_race', 'Race') ?></th>
165 <th class="is-num"><?= t_default('acp.crted.col_actions', 'Actions') ?></th>
166 </tr>
167 </thead>
168 <tbody>
169 <?php foreach ($list as $c): ?>
170 <?php $cName = (string)($c['name'] ?? ''); ?>
171 <tr data-acp-search="<?= h(strtolower($cName)) ?>">
172 <td><?= h($cName) ?></td>
173 <td class="is-num"><?= (int)($c['health'] ?? 0) ?></td>
174 <td class="is-num"><?= (int)($c['experience'] ?? 0) ?></td>
175 <td class="is-muted"><?= h((string)($c['race'] ?? '')) ?></td>
176 <td class="is-nowrap is-num">
177 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h(acp_url('creatures_editor', array('edit' => $cName))) ?>"><i class="fa fa-pencil"></i></a>
178 <form class="acp-inline-form" method="post" data-confirm="<?= h(t_default('acp.crted.confirm_remove', 'Remove this creature?')) ?>">
179 <?= acp_csrf_field() ?>
180 <input type="hidden" name="crted_action" value="delete">
181 <input type="hidden" name="name" value="<?= h($cName) ?>">
182 <button class="acp-btn acp-btn--red acp-btn--sm" type="submit"><i class="fa fa-times"></i></button>
183 </form>
184 </td>
185 </tr>
186 <?php endforeach; ?>
187 </tbody>
188 </table>
189 </div>
190 <?php else: ?>
191 <?php acp_empty(t_default('acp.crted.empty', 'No creatures published yet.'), 'fa-paw'); ?>
192 <?php endif; ?>
193 </div>
194</section>
195
196<p class="acp-hint"><a href="<?= h(acp_url('serverinfo')) ?>"><i class="fa fa-arrow-left"></i> <?= t_default('acp.crted.back', 'Back to Server Info') ?></a></p>
197
Top