[nostalgia/core/gba] Add sprite support
This commit is contained in:
parent
b32734d727
commit
7e565a3162
@ -23,6 +23,7 @@ endif()
|
|||||||
|
|
||||||
install(
|
install(
|
||||||
FILES
|
FILES
|
||||||
|
config.hpp
|
||||||
consts.hpp
|
consts.hpp
|
||||||
context.hpp
|
context.hpp
|
||||||
core.hpp
|
core.hpp
|
||||||
|
15
src/nostalgia/core/config.hpp
Normal file
15
src/nostalgia/core/config.hpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2020 gary@drinkingtea.net
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nostalgia::core::config {
|
||||||
|
|
||||||
|
constexpr auto GbaSpriteBufferLen = 128;
|
||||||
|
|
||||||
|
}
|
@ -50,13 +50,20 @@ using interrupt_handler = void (*)(void);
|
|||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// Memory Addresses
|
// Memory Addresses
|
||||||
|
|
||||||
#define MEM_PALETTE_BG reinterpret_cast<uint16_t*>(0x05000000)
|
#define MEM_EWRAM_BEGIN reinterpret_cast<uint8_t*>(0x02000000)
|
||||||
#define MEM_PALETTE_SPRITE reinterpret_cast<uint16_t*>(0x05000200)
|
#define MEM_EWRAM_END reinterpret_cast<uint8_t*>(0x0203FFFF)
|
||||||
|
|
||||||
|
#define MEM_IWRAM_BEGIN reinterpret_cast<uint8_t*>(0x03000000)
|
||||||
|
#define MEM_IWRAM_END reinterpret_cast<uint8_t*>(0x03007FFF)
|
||||||
|
|
||||||
|
#define MEM_BG_PALETTE reinterpret_cast<uint16_t*>(0x05000000)
|
||||||
|
#define MEM_SPRITE_PALETTE reinterpret_cast<uint16_t*>(0x05000200)
|
||||||
|
|
||||||
typedef uint16_t BgMapTile[1024];
|
typedef uint16_t BgMapTile[1024];
|
||||||
#define MEM_BG_MAP reinterpret_cast<BgMapTile*>(0x06000000)
|
#define MEM_BG_TILES reinterpret_cast<BgMapTile*>(0x06000000)
|
||||||
|
#define MEM_BG_MAP reinterpret_cast<BgMapTile*>(0x0600e000)
|
||||||
|
|
||||||
|
#define MEM_SPRITE_TILES reinterpret_cast<uint8_t*>(0x06010000)
|
||||||
|
#define MEM_OAM reinterpret_cast<uint64_t*>(0x07000000)
|
||||||
|
|
||||||
#define MEM_ROM reinterpret_cast<char*>(0x08000000)
|
#define MEM_ROM reinterpret_cast<char*>(0x08000000)
|
||||||
|
|
||||||
#define MEM_WRAM_BEGIN reinterpret_cast<uint8_t*>(0x02000000)
|
|
||||||
#define MEM_WRAM_END reinterpret_cast<uint8_t*>(0x0203FFFF)
|
|
||||||
|
@ -14,14 +14,12 @@
|
|||||||
#include <nostalgia/core/gfx.hpp>
|
#include <nostalgia/core/gfx.hpp>
|
||||||
|
|
||||||
#include "addresses.hpp"
|
#include "addresses.hpp"
|
||||||
|
#include "irq.hpp"
|
||||||
|
#include "gfx.hpp"
|
||||||
|
|
||||||
namespace nostalgia::core {
|
namespace nostalgia::core {
|
||||||
|
|
||||||
constexpr auto GBA_TILE_COLUMNS = 32;
|
constexpr auto GbaTileColumns = 32;
|
||||||
|
|
||||||
using SpriteAttr = uint16_t[4];
|
|
||||||
|
|
||||||
SpriteAttr spriteBuffer[128];
|
|
||||||
|
|
||||||
struct GbaPaletteTarget {
|
struct GbaPaletteTarget {
|
||||||
static constexpr auto TypeName = NostalgiaPalette::TypeName;
|
static constexpr auto TypeName = NostalgiaPalette::TypeName;
|
||||||
@ -88,8 +86,7 @@ ox::Error initGfx(Context*) {
|
|||||||
/* Background 0 -\|| */
|
/* Background 0 -\|| */
|
||||||
/* Objects -----\||| */
|
/* Objects -----\||| */
|
||||||
/* |||| */
|
/* |||| */
|
||||||
REG_DISPCNT = 0x1100;
|
REG_DISPCNT = 0x1101;
|
||||||
|
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,11 +123,10 @@ ox::Error initConsole(Context *ctx) {
|
|||||||
ox::FileStore32 fs(rom, 32 * ox::units::MB);
|
ox::FileStore32 fs(rom, 32 * ox::units::MB);
|
||||||
ctx->rom = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
ctx->rom = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
||||||
}
|
}
|
||||||
return loadTileSheet(ctx, TileSheetSpace::Background, 0, TilesheetAddr, PaletteAddr);
|
return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error loadTileSheet(Context *ctx,
|
ox::Error loadBgTileSheet(Context *ctx,
|
||||||
TileSheetSpace,
|
|
||||||
int section,
|
int section,
|
||||||
ox::FileAddress tilesheetAddr,
|
ox::FileAddress tilesheetAddr,
|
||||||
ox::FileAddress paletteAddr) {
|
ox::FileAddress paletteAddr) {
|
||||||
@ -139,25 +135,84 @@ ox::Error loadTileSheet(Context *ctx,
|
|||||||
auto [ts, tserr] = ctx->rom->read(tilesheetAddr);
|
auto [ts, tserr] = ctx->rom->read(tilesheetAddr);
|
||||||
oxReturnError(tserr);
|
oxReturnError(tserr);
|
||||||
GbaTileMapTarget target;
|
GbaTileMapTarget target;
|
||||||
target.pal.palette = &MEM_PALETTE_BG[section];
|
target.pal.palette = &MEM_BG_PALETTE[section];
|
||||||
target.bgCtl = &bgCtl(section);
|
target.bgCtl = &bgCtl(section);
|
||||||
target.tileMap = &ox::bit_cast<uint16_t*>(MEM_BG_MAP)[section * 512];
|
target.tileMap = &ox::bit_cast<uint16_t*>(MEM_BG_TILES)[section * 512];
|
||||||
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
||||||
// load external palette if available
|
// load external palette if available
|
||||||
if (paletteAddr) {
|
if (paletteAddr) {
|
||||||
paletteAddr = target.defaultPalette;
|
|
||||||
}
|
|
||||||
auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr);
|
auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr);
|
||||||
oxReturnError(palStatErr);
|
oxReturnError(palStatErr);
|
||||||
auto [pal, palErr] = ctx->rom->read(paletteAddr);
|
auto [pal, palErr] = ctx->rom->read(paletteAddr);
|
||||||
oxReturnError(palErr);
|
oxReturnError(palErr);
|
||||||
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
||||||
|
}
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error loadSpriteTileSheet(Context *ctx,
|
||||||
|
int section,
|
||||||
|
ox::FileAddress tilesheetAddr,
|
||||||
|
ox::FileAddress paletteAddr) {
|
||||||
|
auto [tsStat, tsStatErr] = ctx->rom->stat(tilesheetAddr);
|
||||||
|
oxReturnError(tsStatErr);
|
||||||
|
auto [ts, tserr] = ctx->rom->read(tilesheetAddr);
|
||||||
|
oxReturnError(tserr);
|
||||||
|
GbaTileMapTarget target;
|
||||||
|
target.pal.palette = &MEM_SPRITE_PALETTE[section];
|
||||||
|
target.bgCtl = &bgCtl(section);
|
||||||
|
target.tileMap = &ox::bit_cast<uint16_t*>(MEM_SPRITE_TILES)[section * 512];
|
||||||
|
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
||||||
|
// load external palette if available
|
||||||
|
if (paletteAddr) {
|
||||||
|
auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr);
|
||||||
|
oxReturnError(palStatErr);
|
||||||
|
auto [pal, palErr] = ctx->rom->read(paletteAddr);
|
||||||
|
oxReturnError(palErr);
|
||||||
|
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
||||||
|
}
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error loadBgPalette(Context *ctx, int section, ox::FileAddress paletteAddr) {
|
||||||
|
GbaPaletteTarget target;
|
||||||
|
target.palette = &MEM_BG_PALETTE[section];
|
||||||
|
auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr);
|
||||||
|
oxReturnError(palStatErr);
|
||||||
|
auto [pal, palErr] = ctx->rom->read(paletteAddr);
|
||||||
|
oxReturnError(palErr);
|
||||||
|
oxReturnError(ox::readMC(pal, palStat.size, &target));
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error loadSpritePalette(Context *ctx, int section, ox::FileAddress paletteAddr) {
|
||||||
|
GbaPaletteTarget target;
|
||||||
|
target.palette = &MEM_SPRITE_PALETTE[section];
|
||||||
|
auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr);
|
||||||
|
oxReturnError(palStatErr);
|
||||||
|
auto [pal, palErr] = ctx->rom->read(paletteAddr);
|
||||||
|
oxReturnError(palErr);
|
||||||
|
oxReturnError(ox::readMC(pal, palStat.size, &target));
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do NOT use Context in the GBA version of this function.
|
// Do NOT use Context in the GBA version of this function.
|
||||||
void setTile(Context*, int layer, int column, int row, uint8_t tile) {
|
void setTile(Context*, int layer, int column, int row, uint8_t tile) {
|
||||||
MEM_BG_MAP[28 + layer][row * GBA_TILE_COLUMNS + column] = tile;
|
MEM_BG_MAP[layer][row * GbaTileColumns + column] = tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSprite(uint8_t idx, uint8_t x, uint8_t y, uint8_t tileIdx) {
|
||||||
|
// block until g_spriteUpdates is less than buffer len
|
||||||
|
while (g_spriteUpdates >= config::GbaSpriteBufferLen);
|
||||||
|
GbaSpriteAttrUpdate oa;
|
||||||
|
oa.attr0 = static_cast<uint16_t>(y & ox::onMask<uint8_t>(7))
|
||||||
|
| (static_cast<uint16_t>(1) << 10); // enable alpha
|
||||||
|
oa.attr1 = static_cast<uint16_t>(x);
|
||||||
|
oa.attr2 = static_cast<uint16_t>(tileIdx & ox::onMask<uint16_t>(8));
|
||||||
|
oa.idx = idx;
|
||||||
|
REG_IE &= ~Int_vblank; // disable vblank interrupt handler
|
||||||
|
g_spriteBuffer[g_spriteUpdates++] = oa;
|
||||||
|
REG_IE |= Int_vblank; // enable vblank interrupt handler
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
29
src/nostalgia/core/gba/gfx.hpp
Normal file
29
src/nostalgia/core/gba/gfx.hpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2020 gary@drinkingtea.net
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ox/std/stddef.hpp>
|
||||||
|
#include <ox/std/types.hpp>
|
||||||
|
|
||||||
|
#include <nostalgia/core/config.hpp>
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
struct OX_ALIGN8 GbaSpriteAttrUpdate {
|
||||||
|
uint16_t attr0 = 0;
|
||||||
|
uint16_t attr1 = 0;
|
||||||
|
uint16_t attr2 = 0;
|
||||||
|
uint16_t idx = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
extern volatile uint16_t g_spriteUpdates;
|
||||||
|
extern GbaSpriteAttrUpdate g_spriteBuffer[config::GbaSpriteBufferLen];
|
||||||
|
|
||||||
|
}
|
@ -9,10 +9,35 @@
|
|||||||
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
|
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
|
||||||
// here
|
// here
|
||||||
|
|
||||||
|
#include <nostalgia/core/config.hpp>
|
||||||
|
|
||||||
|
#include "addresses.hpp"
|
||||||
|
#include "gfx.hpp"
|
||||||
#include "irq.hpp"
|
#include "irq.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
volatile uint16_t g_spriteUpdates = 0;
|
||||||
|
GbaSpriteAttrUpdate g_spriteBuffer[config::GbaSpriteBufferLen];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace nostalgia::core;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
void nostalgia_core_isr_vblank() {
|
||||||
|
// copy g_spriteUpdates to allow it to use a register instead of reading
|
||||||
|
// from memory every iteration of the loop, needed because g_spriteUpdates
|
||||||
|
// is volatile
|
||||||
|
const auto updates = g_spriteUpdates;
|
||||||
|
for (uint16_t i = 0; i < updates; ++i) {
|
||||||
|
auto &oa = g_spriteBuffer[i];
|
||||||
|
MEM_OAM[oa.idx] = *reinterpret_cast<uint64_t*>(&oa);
|
||||||
|
}
|
||||||
|
g_spriteUpdates = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void nostalgia_core_isr_timer0() {
|
void nostalgia_core_isr_timer0() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +50,4 @@ void nostalgia_core_isr_timer2() {
|
|||||||
void nostalgia_core_isr_timer3() {
|
void nostalgia_core_isr_timer3() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void nostalgia_core_isr_vblank() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,6 @@ isr:
|
|||||||
// Interrupt Table End //
|
// Interrupt Table End //
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
isr_end:
|
isr_end:
|
||||||
// restore lr from before the Interrupt Table
|
// restore lr from before the Interrupt Table
|
||||||
pop {lr}
|
pop {lr}
|
||||||
|
@ -75,7 +75,12 @@ ox::Error model(T *io, NostalgiaGraphic *ng) {
|
|||||||
/**
|
/**
|
||||||
* @param section describes which section of the selected TileSheetSpace to use (e.g. MEM_PALLETE_BG[section])
|
* @param section describes which section of the selected TileSheetSpace to use (e.g. MEM_PALLETE_BG[section])
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] ox::Error loadTileSheet(Context *ctx, TileSheetSpace tss, int section, ox::FileAddress tilesheet, ox::FileAddress palette = nullptr);
|
[[nodiscard]] ox::Error loadBgTileSheet(Context *ctx, int section, ox::FileAddress tilesheet, ox::FileAddress palette = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] ox::Error loadSpriteTileSheet(Context *ctx,
|
||||||
|
int section,
|
||||||
|
ox::FileAddress tilesheetAddr,
|
||||||
|
ox::FileAddress paletteAddr);
|
||||||
|
|
||||||
[[nodiscard]] Color32 toColor32(Color16 nc) noexcept;
|
[[nodiscard]] Color32 toColor32(Color16 nc) noexcept;
|
||||||
|
|
||||||
@ -120,4 +125,6 @@ void puts(Context *ctx, int column, int row, const char *str);
|
|||||||
|
|
||||||
void setTile(Context *ctx, int layer, int column, int row, uint8_t tile);
|
void setTile(Context *ctx, int layer, int column, int row, uint8_t tile);
|
||||||
|
|
||||||
|
void setSprite(uint8_t idx, uint8_t x, uint8_t y, uint8_t tileIdx);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,7 @@ ox::Error initConsole(Context*) {
|
|||||||
return OxError(1);
|
return OxError(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error loadTileSheet(Context*,
|
ox::Error loadBgTileSheet(Context*,
|
||||||
TileSheetSpace,
|
|
||||||
int,
|
int,
|
||||||
ox::FileAddress,
|
ox::FileAddress,
|
||||||
ox::FileAddress) {
|
ox::FileAddress) {
|
||||||
|
@ -72,7 +72,7 @@ ox::Error shutdownGfx(Context *ctx) {
|
|||||||
|
|
||||||
ox::Error initConsole(Context *ctx) {
|
ox::Error initConsole(Context *ctx) {
|
||||||
constexpr auto TilesheetAddr = "/TileSheets/Charset.ng";
|
constexpr auto TilesheetAddr = "/TileSheets/Charset.ng";
|
||||||
return loadTileSheet(ctx, TileSheetSpace::Background, 0, TilesheetAddr);
|
return loadBgTileSheet(ctx, 0, TilesheetAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Color createSDL_Color(Color16 nc) {
|
SDL_Color createSDL_Color(Color16 nc) {
|
||||||
@ -93,8 +93,7 @@ SDL_Palette *createSDL_Palette(const NostalgiaPalette &npal) {
|
|||||||
return pal;
|
return pal;
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error loadTileSheet(Context *ctx,
|
ox::Error loadBgTileSheet(Context *ctx,
|
||||||
TileSheetSpace tss,
|
|
||||||
int section,
|
int section,
|
||||||
ox::FileAddress tilesheetPath,
|
ox::FileAddress tilesheetPath,
|
||||||
ox::FileAddress palettePath) {
|
ox::FileAddress palettePath) {
|
||||||
@ -129,13 +128,18 @@ ox::Error loadTileSheet(Context *ctx,
|
|||||||
SDL_FreePalette(sdlPalette);
|
SDL_FreePalette(sdlPalette);
|
||||||
|
|
||||||
auto sectionIdx = static_cast<unsigned>(section);
|
auto sectionIdx = static_cast<unsigned>(section);
|
||||||
if (tss == TileSheetSpace::Background) {
|
|
||||||
if (id->bgTextures[sectionIdx]) {
|
if (id->bgTextures[sectionIdx]) {
|
||||||
SDL_DestroyTexture(id->bgTextures[sectionIdx]);
|
SDL_DestroyTexture(id->bgTextures[sectionIdx]);
|
||||||
}
|
}
|
||||||
id->bgTextures[sectionIdx] = texture;
|
id->bgTextures[sectionIdx] = texture;
|
||||||
|
|
||||||
|
return OxError(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ox::Error loadSpriteTileSheet(Context*,
|
||||||
|
int,
|
||||||
|
ox::FileAddress,
|
||||||
|
ox::FileAddress) {
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,4 +200,7 @@ void setTile(Context *ctx, int layer, int column, int row, uint8_t tile) {
|
|||||||
id->bgTileMaps[z][y][x] = tile;
|
id->bgTileMaps[z][y][x] = tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSprite(uint8_t, uint8_t, uint8_t, uint8_t) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,11 @@ ox::Error run(ox::FileSystem *fs) {
|
|||||||
//Zone zone;
|
//Zone zone;
|
||||||
//oxReturnError(zone.init(&ctx, Bounds{0, 0, 40, 40}, "/TileSheets/Charset.ng", "/Palettes/Charset.npal"));
|
//oxReturnError(zone.init(&ctx, Bounds{0, 0, 40, 40}, "/TileSheets/Charset.ng", "/Palettes/Charset.npal"));
|
||||||
//zone.draw(&ctx);
|
//zone.draw(&ctx);
|
||||||
|
constexpr auto TileSheetAddr = "/TileSheets/Charset.ng";
|
||||||
|
constexpr auto PaletteAddr = "/Palettes/Charset.npal";
|
||||||
|
oxReturnError(core::loadSpriteTileSheet(&ctx, 0, TileSheetAddr, PaletteAddr));
|
||||||
oxReturnError(core::initConsole(&ctx));
|
oxReturnError(core::initConsole(&ctx));
|
||||||
|
core::setSprite(0, 50, 50, 7);
|
||||||
core::puts(&ctx, 10, 9, "DOPENESS!!!");
|
core::puts(&ctx, 10, 9, "DOPENESS!!!");
|
||||||
oxReturnError(core::run(&ctx));
|
oxReturnError(core::run(&ctx));
|
||||||
oxReturnError(core::shutdownGfx(&ctx));
|
oxReturnError(core::shutdownGfx(&ctx));
|
||||||
|
@ -17,7 +17,7 @@ ox::Error Zone::init(Context *ctx, Bounds bnds, ox::FileAddress tileSheet, ox::F
|
|||||||
const auto size = static_cast<std::size_t>(bnds.width * bnds.height);
|
const auto size = static_cast<std::size_t>(bnds.width * bnds.height);
|
||||||
m_tiles = new Tile[size];
|
m_tiles = new Tile[size];
|
||||||
m_bounds = bnds;
|
m_bounds = bnds;
|
||||||
return core::loadTileSheet(ctx, core::TileSheetSpace::Background, 0, tileSheet, palette);
|
return core::loadBgTileSheet(ctx, 0, tileSheet, palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
Zone::~Zone() {
|
Zone::~Zone() {
|
||||||
|
Loading…
Reference in New Issue
Block a user