GetNewDrop's weighted item-index selection used a do-while that
increments before its first RunningTotal check, so index 0 (the first
item slot in every drop table) could never be selected -- for the 51
drop tables that only populate item slot 1 (all others zero), this
silently discarded every roll ("item at itemindex does not exist"),
so those mobs/maps never dropped an item at all. Switch to a pre-check
while loop so index 0 is reachable.
Also: battle.cpp's kill-drop loop runs GetDrop()/AddDrop() exactly
Stats->itemdroprate + Config.DROP_RATE times, not as a probability --
with the shipped drop_rate=2 every kill produced two independently
rolled drops (hence always-doubled Zuly boxes for characters with no
item-drop-rate gear). Default drop_rate to 1 so a kill yields one
roll by default; gear/GM commands that raise itemdroprate still grant
extra rolls as designed.
Also default dropdelay to 0: the killed-monster's loot became visible
to clients only after Config.DropDelay (1s) *plus* the next
VisualDelay tick (200ms), giving a ~1.2s lag before drops appeared.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Client/, 3Ddata*, KTRose_pre-evo.zip are large local-only assets used
for end-to-end login/game testing, not part of the server source tree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds an nginx:alpine service serving webtools/ (item/map lookup
frontend) on port 80 alongside the existing login/char/world stack.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
accounts.lastlogin/logtime had no defaults and were NOT NULL, but the
auto-create-account INSERT never set them, so new accounts failed
under MariaDB strict mode (silently accepted under the old MySQL
5.0 target). Insert explicit empty/zero values and give lastlogin a
default; also widen it to varchar(32) to match the runtime.sql fix
already applied to the starttime column.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GetNewDrop() allocated newdrop up front but returned NULL without
freeing it on every rejection path (failed drop-rate check, missing
drop table, unknown item type, etc.), leaking on every non-drop roll.
Free it on all early-return paths, and only assign a clientid once a
drop is actually going to be returned.
CPacket::AddByte/Word/DWord/QWord/Float now bounds-check against the
buffer before writing, preventing an overflow if a packet is built
past its allocated size.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Several quest-loading loops used <=N over arrays sized N (Variables,
Switches, Items, EpisodeVar, JobVar, PlanetVar, UnionVar), writing one
element past the end on every character load. Also guard STB_QUEST
table lookups with a rowcount bounds check before indexing by
QuestID, which could otherwise read out of bounds for stale/invalid
quest ids.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
POSIX clock() sums CPU time across every thread in the process, unlike
Windows' clock() which tracks wall-clock milliseconds. With several
worker threads running, respawn/spawn/AI timers based on clock() raced
far ahead of real elapsed time. Switch spawn-timer bookkeeping to
time(NULL)/time_t (seconds) throughout the world server, and add a
portable clock() shim in sockets.h (backed by CLOCK_MONOTONIC,
millisecond resolution) for remaining call sites so CLOCKS_PER_SEC==1000
still holds like it did on Windows.
Also: fix MapDelay/VisualDelay/WorldDelay being passed to usleep() (us)
without converting from their configured milliseconds; fix the shutdown
timer likewise; stop leaking the temporary actor used to run an NPC's
AIP script; fix CleanDrops() skipping the element that shifts into
position after an erase.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>