Created
March 9, 2026 21:45
-
-
Save EncodeTheCode/416fcdcd3768137e949f4acb6915f9b8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # weapon_cfg_singlefile.py | |
| import struct, mmap | |
| from collections import namedtuple | |
| from array import array | |
| Weapon = namedtuple("Weapon", "id name ammo fire_rate weapon_type sound damage") | |
| # Header: magic(4) 'WCFG', version(1), 3 reserved bytes, count(uint32), names_size(uint32), sounds_size(uint32) | |
| _HEADER_STRUCT = struct.Struct("<4sB3xIII") | |
| _HEADER_SIZE = _HEADER_STRUCT.size | |
| # Record (fixed-size 28 bytes): | |
| # id:uint32, name_off:uint32, name_len:uint16, ammo:uint16, | |
| # fire_rate:float32, weapon_type:uint8, pad:1, sound_off:uint32, sound_len:uint16, damage:float32 | |
| _RECORD_STRUCT = struct.Struct("<IIHHfBxIHf") | |
| _RECORD_SIZE = _RECORD_STRUCT.size | |
| _MAGIC = b"WCFG" | |
| _VERSION = 1 | |
| # default enum map (numeric -> string) | |
| DEFAULT_TYPES = { | |
| 0: "unknown", | |
| 1: "melee", | |
| 2: "firearm", | |
| 3: "taser", | |
| 4: "hand-taser", | |
| } | |
| class WeaponConfig: | |
| """Single-file, mmap-based loader that also exposes compact numeric arrays for fast direct access.""" | |
| __slots__ = ("_mm", "_count", "_names_off", "_sounds_off", | |
| "ids", "ammo", "fire_rates", "types", "damages", | |
| "id_to_index", "_types_map") | |
| def __init__(self, path: str, types_map: dict = None, build_arrays: bool = True): | |
| """ | |
| path: path to binary .wcfg file | |
| types_map: optional dict mapping numeric enum -> string | |
| build_arrays: when True (default) build array.array buffers for quick numeric access | |
| """ | |
| f = open(path, "rb") | |
| try: | |
| mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) | |
| finally: | |
| f.close() | |
| self._mm = mm | |
| magic, ver, count, names_size, sounds_size = _HEADER_STRUCT.unpack_from(mm, 0) | |
| if magic != _MAGIC: | |
| raise ValueError("bad magic") | |
| if ver != _VERSION: | |
| raise ValueError("unsupported version") | |
| self._count = count | |
| records_bytes = count * _RECORD_SIZE | |
| self._names_off = _HEADER_SIZE + records_bytes | |
| self._sounds_off = self._names_off + names_size | |
| self._types_map = types_map or DEFAULT_TYPES | |
| # prepare containers (arrays are compact C arrays) | |
| # ids: uint32, ammo: uint16, fire_rates: float32, types: uint8, damages: float32 | |
| self.ids = array("I") # unsigned 32-bit | |
| self.ammo = array("H") # unsigned 16-bit | |
| self.fire_rates = array("f") # 32-bit float | |
| self.types = array("B") # unsigned 8-bit | |
| self.damages = array("f") # 32-bit float | |
| self.id_to_index = {} | |
| # Build arrays and index in one pass (done once at load) | |
| base = _HEADER_SIZE | |
| for i in range(count): | |
| off = base + i * _RECORD_SIZE | |
| # unpack returns (id, name_off, name_len, ammo, fire_rate, wtype, sound_off, sound_len, damage) | |
| id_, name_rel, name_len, ammo_v, fire_rate_v, wtype_v, sound_off, sound_len, damage_v = _RECORD_STRUCT.unpack_from(mm, off) | |
| self.id_to_index[id_] = i | |
| if build_arrays: | |
| self.ids.append(id_) | |
| self.ammo.append(ammo_v) | |
| self.fire_rates.append(fire_rate_v) | |
| self.types.append(wtype_v & 0xFF) | |
| self.damages.append(damage_v) | |
| def close(self): | |
| mm = getattr(self, "_mm", None) | |
| if mm: | |
| mm.close() | |
| self._mm = None | |
| def __len__(self): | |
| return self._count | |
| # --- direct numeric access helpers --------------------------------- | |
| # You can index the arrays directly: | |
| # cfg.ids[5], cfg.fire_rates[5], cfg.ammo[5], cfg.types[5], cfg.damages[5] | |
| # Or use convenience lookups: | |
| def index_of(self, weapon_id: int): | |
| """Return zero-based index of the weapon with given id (KeyError if not present).""" | |
| return self.id_to_index[weapon_id] | |
| def fire_rate_by_id(self, weapon_id: int) -> float: | |
| """Direct fast lookup of fire_rate by id.""" | |
| idx = self.id_to_index[weapon_id] | |
| return self.fire_rates[idx] | |
| def id_by_index(self, index: int) -> int: | |
| return self.ids[index] | |
| # --- higher-level record access (lazy-decode strings) ---------------- | |
| def _unpack_at_index(self, index: int) -> Weapon: | |
| if index < 0 or index >= self._count: | |
| raise IndexError("index out of range") | |
| rec_off = _HEADER_SIZE + index * _RECORD_SIZE | |
| id_, name_rel, name_len, ammo_v, fire_rate_v, wtype_v, sound_off, sound_len, damage_v = _RECORD_STRUCT.unpack_from(self._mm, rec_off) | |
| name = "" | |
| if name_len: | |
| start = self._names_off + name_rel | |
| name = self._mm[start: start + name_len].decode("utf-8") | |
| sound = "" | |
| if sound_len: | |
| sstart = self._sounds_off + sound_off | |
| sound = self._mm[sstart: sstart + sound_len].decode("utf-8") | |
| wtype_str = self._types_map.get(wtype_v, f"type#{wtype_v}") | |
| return Weapon(id_, name, ammo_v, fire_rate_v, wtype_str, sound, damage_v) | |
| def get_by_index(self, index: int) -> Weapon: | |
| return self._unpack_at_index(index) | |
| def get_by_id(self, weapon_id: int) -> Weapon: | |
| return self._unpack_at_index(self.id_to_index[weapon_id]) | |
| def iter_records(self): | |
| """Iterator that yields Weapon namedtuples (lazy-decoding names/sounds).""" | |
| for i in range(self._count): | |
| yield self._unpack_at_index(i) | |
| # ------------------- tiny writer (asset-build step) --------------------- | |
| def write_weapon_file(path: str, weapons): | |
| """ | |
| weapons: iterable of dicts with keys: | |
| id (int), name (str), ammo (int), fire_rate (float), | |
| type (int), sound (str), damage (float) | |
| Example: | |
| {"id":1,"name":"Knife","ammo":0,"fire_rate":0.0,"type":1,"sound":"knife_stab","damage":18.0} | |
| """ | |
| names = bytearray() | |
| sounds = bytearray() | |
| records = bytearray() | |
| for w in weapons: | |
| id_ = int(w["id"]) | |
| name_b = (w.get("name") or "").encode("utf-8") | |
| name_off = len(names) | |
| name_len = len(name_b) | |
| names.extend(name_b) | |
| sound_b = (w.get("sound") or "").encode("utf-8") | |
| sound_off = len(sounds) | |
| sound_len = len(sound_b) | |
| sounds.extend(sound_b) | |
| ammo = int(w.get("ammo", 0)) & 0xFFFF | |
| fire_rate = float(w.get("fire_rate", 0.0)) | |
| wtype = int(w.get("type", 0)) & 0xFF | |
| damage = float(w.get("damage", 0.0)) | |
| records.extend(_RECORD_STRUCT.pack( | |
| id_, name_off, name_len, ammo, fire_rate, wtype, sound_off, sound_len, damage | |
| )) | |
| header = _HEADER_STRUCT.pack(_MAGIC, _VERSION, len(records) // _RECORD_SIZE, len(names), len(sounds)) | |
| with open(path, "wb") as f: | |
| f.write(header) | |
| f.write(records) | |
| f.write(names) | |
| f.write(sounds) | |
| # ------------------- quick example usage ------------------------------- | |
| if __name__ == "__main__": | |
| # build a test file (run once during build) | |
| sample = [ | |
| {"id":1,"name":"Combat Knife","ammo":0,"fire_rate":0.0,"type":1,"sound":"knife_stab","damage":18.0}, | |
| {"id":2,"name":"9mm Pistol","ammo":15,"fire_rate":0.2,"type":2,"sound":"pistol_shot","damage":12.0}, | |
| {"id":3,"name":"Taser","ammo":0,"fire_rate":0.5,"type":3,"sound":"taser_zap","damage":5.0}, | |
| {"id":4,"name":"Hand-Taser","ammo":0,"fire_rate":0.6,"type":4,"sound":"handtaser_zap","damage":4.5}, | |
| ] | |
| write_weapon_file("weapons.wcfg", sample) | |
| cfg = WeaponConfig("weapons.wcfg") | |
| # Direct array access (very fast, O(1), zero unpack cost) | |
| print("ids:", list(cfg.ids)) | |
| print("fire_rates:", list(cfg.fire_rates)) | |
| # get fire_rate by id (fast) | |
| print("fire_rate of id=2 ->", cfg.fire_rate_by_id(2)) | |
| # get full record (lazy-decode strings) | |
| print(cfg.get_by_id(2)) | |
| # iterate records | |
| for r in cfg.iter_records(): | |
| print(r) | |
| cfg.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment