mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-22 23:18:01 +03:00
452 lines
11 KiB
C
452 lines
11 KiB
C
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#include "system4.h"
|
|
#include "system4/string.h"
|
|
#include "system4/utfsjis.h"
|
|
#include "vm/heap.h"
|
|
#include "vm/page.h"
|
|
#include "gfx/font.h"
|
|
#include "sprite.h"
|
|
#include "xsystem4.h"
|
|
#include "CharSpriteManager.h"
|
|
#include "iarray.h"
|
|
|
|
/*
|
|
* NOTE: This is not a library on it's own, but rather part of
|
|
* StoatSpriteEngine/ChipmunkSpriteEngine.
|
|
*/
|
|
|
|
struct charsprite {
|
|
struct sact_sprite sp;
|
|
// NOTE: even though we only render a single character, we need to store an
|
|
// arbitrary string because CharSprite_GetChar returns it.
|
|
struct string *ch;
|
|
struct text_style ts;
|
|
// NOTE: this is needed because we have to hide all sprites after loading
|
|
// regardless of their original visibility. When Rebuild is called,
|
|
// this value is used to correctly set the visibility.
|
|
bool show;
|
|
};
|
|
|
|
struct charsprite_manager {
|
|
unsigned size;
|
|
struct charsprite **sprites;
|
|
unsigned nr_free;
|
|
int *free;
|
|
};
|
|
|
|
static struct charsprite_manager chars = {0};
|
|
|
|
// XXX: after CharSpriteManager_Load, CharSpriteManager_Rebuild must be called to
|
|
// make the loaded data visible. It is stored here until then.
|
|
static struct charsprite_manager load_chars = {0};
|
|
static bool loaded = false;
|
|
|
|
static void grow_chars(unsigned new_size) {
|
|
assert(new_size > chars.size);
|
|
chars.sprites = xrealloc_array(chars.sprites, chars.size, new_size, sizeof(struct charsprite*));
|
|
chars.free = xrealloc_array(chars.free, chars.size, new_size, sizeof(int));
|
|
|
|
for (unsigned i = chars.size; i < new_size; i++) {
|
|
chars.free[chars.nr_free++] = i;
|
|
}
|
|
|
|
chars.size = new_size;
|
|
}
|
|
|
|
static int get_handle(void)
|
|
{
|
|
if (!chars.nr_free) {
|
|
grow_chars(chars.size ? chars.size*2 : 256);
|
|
}
|
|
return chars.free[--chars.nr_free];
|
|
}
|
|
|
|
static bool handle_valid(int handle)
|
|
{
|
|
return handle >= 0 && (unsigned)handle < chars.size && chars.sprites[handle];
|
|
}
|
|
|
|
static void check_handle(int handle)
|
|
{
|
|
if (!handle_valid(handle))
|
|
VM_ERROR("Invalid CharSprite handle: %d", handle);
|
|
}
|
|
|
|
static struct charsprite *charsprite_get(int handle)
|
|
{
|
|
check_handle(handle);
|
|
return chars.sprites[handle];
|
|
}
|
|
|
|
static bool charsprite_allocated(struct charsprite *cs)
|
|
{
|
|
return cs->ch != NULL;
|
|
}
|
|
|
|
int CharSpriteManager_CreateHandle(void)
|
|
{
|
|
int handle = get_handle();
|
|
assert(!chars.sprites[handle]);
|
|
chars.sprites[handle] = xcalloc(1, sizeof(struct charsprite));
|
|
sprite_init(&chars.sprites[handle]->sp);
|
|
chars.sprites[handle]->ts.scale_x = 1.0f;
|
|
return handle;
|
|
}
|
|
|
|
static void charsprite_free(struct charsprite *cs)
|
|
{
|
|
sprite_free(&cs->sp);
|
|
if (cs->ch) {
|
|
free_string(cs->ch);
|
|
}
|
|
free(cs);
|
|
}
|
|
|
|
bool CharSpriteManager_ReleaseHandle(int handle)
|
|
{
|
|
if (!handle_valid(handle))
|
|
return false;
|
|
|
|
charsprite_free(chars.sprites[handle]);
|
|
chars.sprites[handle] = NULL;
|
|
chars.free[chars.nr_free++] = handle;
|
|
return true;
|
|
}
|
|
|
|
static void charsprite_manager_free(struct charsprite_manager *csm)
|
|
{
|
|
for (unsigned i = 0; i < csm->size; i++) {
|
|
if (csm->sprites[i])
|
|
charsprite_free(csm->sprites[i]);
|
|
}
|
|
free(csm->sprites);
|
|
free(csm->free);
|
|
memset(csm, 0, sizeof(*csm));
|
|
}
|
|
|
|
void CharSpriteManager_Clear(void)
|
|
{
|
|
for (unsigned i = 0; i < chars.size; i++) {
|
|
CharSpriteManager_ReleaseHandle(i);
|
|
}
|
|
}
|
|
|
|
static int extract_sjis_char(char *src, char *dst)
|
|
{
|
|
if (SJIS_2BYTE(*src)) {
|
|
dst[0] = src[0];
|
|
dst[1] = src[1];
|
|
dst[2] = '\0';
|
|
return 2;
|
|
}
|
|
dst[0] = src[0];
|
|
dst[1] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static void charsprite_render(struct charsprite *cs)
|
|
{
|
|
char ch[3];
|
|
extract_sjis_char(cs->ch->text, ch);
|
|
|
|
// XXX: this is handled here and not in gfx_render_text because of the
|
|
// width calculation below
|
|
if (ch[0] == '}' && game_rance02_mg) {
|
|
ch[0] = 0x81;
|
|
ch[1] = 0x5c;
|
|
ch[2] = 0;
|
|
}
|
|
|
|
int w = ceilf(gfx_size_char(&cs->ts, ch));
|
|
int h = cs->ts.size + cs->ts.size/2;
|
|
sprite_init_color(&cs->sp, w, h, 0, 0, 0, 0);
|
|
sprite_get_texture(&cs->sp); // XXX: force initialization of texture
|
|
gfx_render_text(&cs->sp.texture, 0.0f, 0, ch, &cs->ts, false);
|
|
sprite_dirty(&cs->sp);
|
|
}
|
|
|
|
void CharSpriteManager_Rebuild(void)
|
|
{
|
|
if (!loaded)
|
|
return;
|
|
|
|
charsprite_manager_free(&chars);
|
|
chars = load_chars;
|
|
|
|
// reset visibility
|
|
for (unsigned i = 0; i < chars.size; i++) {
|
|
if (!chars.sprites[i])
|
|
continue;
|
|
charsprite_render(chars.sprites[i]);
|
|
sprite_set_show(&chars.sprites[i]->sp, chars.sprites[i]->show);
|
|
}
|
|
|
|
memset(&load_chars, 0, sizeof(load_chars));
|
|
loaded = false;
|
|
}
|
|
|
|
bool CharSpriteManager_Save(struct page **iarray)
|
|
{
|
|
// 'C'
|
|
// 'S'
|
|
// 'M'
|
|
// 0x0
|
|
// 0x0
|
|
// 0x1 <- nr handles (NOT necessary the length of the following list)
|
|
// 0x1 <- ???
|
|
// 0x67 <- 'C' (char text)
|
|
// 0 <- '\0'
|
|
// 0 <- m_nType
|
|
// 64 <- m_nSize
|
|
// ...
|
|
// 0 <- x
|
|
// 0 <- y
|
|
// 0 <- z
|
|
// 255 <- alpha rate
|
|
// 1 <- show
|
|
// 2000 <- handle + 2000
|
|
|
|
struct iarray_writer w;
|
|
iarray_init_writer(&w, "CSM");
|
|
iarray_write(&w, 0); // version?
|
|
iarray_write(&w, chars.size);
|
|
|
|
for (unsigned i = 0; i < chars.size; i++) {
|
|
if (!chars.sprites[i])
|
|
continue;
|
|
|
|
struct charsprite *s = chars.sprites[i];
|
|
iarray_write(&w, 1); // ???
|
|
iarray_write_string(&w, s->ch);
|
|
iarray_write(&w, s->ts.face);
|
|
iarray_write(&w, s->ts.size);
|
|
iarray_write(&w, s->ts.color.r);
|
|
iarray_write(&w, s->ts.color.g);
|
|
iarray_write(&w, s->ts.color.b);
|
|
iarray_write(&w, s->ts.color.a);
|
|
//iarray_write_float(&w, s->ts.weight);
|
|
iarray_write_float(&w, 1.0);
|
|
iarray_write_float(&w, s->ts.edge_left);
|
|
iarray_write(&w, s->ts.edge_color.r);
|
|
iarray_write(&w, s->ts.edge_color.g);
|
|
iarray_write(&w, s->ts.edge_color.b);
|
|
iarray_write(&w, s->ts.edge_color.a);
|
|
iarray_write(&w, sprite_get_pos_x(&s->sp));
|
|
iarray_write(&w, sprite_get_pos_y(&s->sp));
|
|
iarray_write(&w, sprite_get_z(&s->sp));
|
|
iarray_write(&w, sprite_get_blend_rate(&s->sp));
|
|
iarray_write(&w, sprite_get_show(&s->sp));
|
|
iarray_write(&w, 2000 + i);
|
|
|
|
// NOTE: Allocated but uninitialized handles are saved with a
|
|
// handle of -1. These persist even after loading and
|
|
// re-saving.
|
|
// I've haven't implemented this as there's no way to know
|
|
// the original handle number after loading anyways. Seems
|
|
// like a memory leak in the original implementation.
|
|
}
|
|
|
|
if (*iarray) {
|
|
delete_page_vars(*iarray);
|
|
free_page(*iarray);
|
|
}
|
|
*iarray = iarray_to_page(&w);
|
|
iarray_free_writer(&w);
|
|
return true;
|
|
}
|
|
|
|
bool CharSpriteManager_Load(struct page **data)
|
|
{
|
|
struct charsprite_manager csm = {0};
|
|
|
|
if (!(*data))
|
|
return false;
|
|
|
|
struct iarray_reader r;
|
|
iarray_init_reader(&r, *data, "CSM");
|
|
if (iarray_read(&r))
|
|
return false;
|
|
|
|
int nr_handles = iarray_read(&r);
|
|
if (nr_handles < 0 || nr_handles > 100000)
|
|
goto error;
|
|
|
|
csm.size = nr_handles;
|
|
csm.sprites = xcalloc(nr_handles, sizeof(struct charsprite*));
|
|
|
|
// load handles
|
|
while (!iarray_end(&r)) {
|
|
// character data
|
|
struct charsprite *cs = xcalloc(1, sizeof(struct charsprite));
|
|
iarray_read(&r); // ???
|
|
cs->ch = iarray_read_string(&r);
|
|
cs->ts.face = iarray_read(&r);
|
|
cs->ts.size = iarray_read(&r);
|
|
cs->ts.color.r = iarray_read(&r);
|
|
cs->ts.color.g = iarray_read(&r);
|
|
cs->ts.color.b = iarray_read(&r);
|
|
cs->ts.color.a = iarray_read(&r);
|
|
cs->ts.weight = FW_NORMAL; iarray_read_float(&r); // FIXME
|
|
text_style_set_edge_width(&cs->ts, iarray_read_float(&r));
|
|
cs->ts.edge_color.r = iarray_read(&r);
|
|
cs->ts.edge_color.g = iarray_read(&r);
|
|
cs->ts.edge_color.b = iarray_read(&r);
|
|
cs->ts.edge_color.a = iarray_read(&r);
|
|
cs->ts.bold_width = 0.0f;
|
|
cs->ts.scale_x = 1.0f;
|
|
cs->ts.space_scale_x = 1.0f;
|
|
cs->ts.font_spacing = 0.0f;
|
|
cs->ts.font_size = NULL;
|
|
|
|
// sprite data
|
|
int x = iarray_read(&r);
|
|
int y = iarray_read(&r);
|
|
sprite_set_show(&cs->sp, false);
|
|
sprite_set_pos(&cs->sp, x, y);
|
|
sprite_set_z(&cs->sp, iarray_read(&r));
|
|
sprite_set_blend_rate(&cs->sp, iarray_read(&r));
|
|
cs->show = iarray_read(&r);
|
|
|
|
// insert charsprite into array
|
|
int handle = iarray_read(&r) - 2000;
|
|
if (handle < 0 || (unsigned)handle >= csm.size || csm.sprites[handle]) {
|
|
sprite_free(&cs->sp);
|
|
free(cs);
|
|
goto error;
|
|
}
|
|
csm.sprites[handle] = cs;
|
|
|
|
if (r.error)
|
|
goto error;
|
|
}
|
|
|
|
// initialize free list
|
|
csm.free = xcalloc(csm.size, sizeof(int));
|
|
for (unsigned i = 0; i < csm.size; i++) {
|
|
if (!csm.sprites[i])
|
|
csm.free[csm.nr_free++] = i;
|
|
}
|
|
|
|
if (loaded) {
|
|
charsprite_manager_free(&load_chars);
|
|
}
|
|
load_chars = csm;
|
|
loaded = true;
|
|
return true;
|
|
error:
|
|
WARNING("CharSpriteManager_Load failed");
|
|
charsprite_manager_free(&csm);
|
|
return false;
|
|
}
|
|
|
|
void CharSprite_Release(int handle)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
sprite_free(&sp->sp);
|
|
}
|
|
|
|
static void CASColor_to_SDL_Color(SDL_Color *color, int page_no)
|
|
{
|
|
struct page *page = heap_get_page(page_no);
|
|
assert(page);
|
|
assert(page->nr_vars == 4);
|
|
color->r = page->values[0].i;
|
|
color->g = page->values[1].i;
|
|
color->b = page->values[2].i;
|
|
color->a = page->values[3].i;
|
|
}
|
|
|
|
static void CASCharSpriteProperty_to_text_style(struct text_style *ts, struct page *page)
|
|
{
|
|
assert(page);
|
|
assert(page->nr_vars == 6);
|
|
|
|
ts->face = page->values[0].i;
|
|
ts->size = page->values[1].i;
|
|
CASColor_to_SDL_Color(&ts->color, page->values[2].i);
|
|
//ts->weight = page->values[3].f;
|
|
ts->weight = FW_NORMAL;
|
|
text_style_set_edge_width(ts, page->values[4].f);
|
|
CASColor_to_SDL_Color(&ts->edge_color, page->values[5].i);
|
|
}
|
|
|
|
void CharSprite_SetChar(int handle, struct string **s, struct page **property)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
CASCharSpriteProperty_to_text_style(&sp->ts, *property);
|
|
|
|
if (charsprite_allocated(sp)) {
|
|
sprite_free(&sp->sp);
|
|
}
|
|
if (sp->ch) {
|
|
free_string(sp->ch);
|
|
sp->ch = NULL;
|
|
}
|
|
if (!(*s)->size) {
|
|
return;
|
|
}
|
|
|
|
sp->ch = string_ref(*s);
|
|
charsprite_render(sp);
|
|
}
|
|
|
|
void CharSprite_SetPos(int handle, int x, int y)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
sprite_set_pos(&sp->sp, x, y);
|
|
}
|
|
|
|
void CharSprite_SetZ(int handle, int z)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
sprite_set_z(&sp->sp, z);
|
|
}
|
|
|
|
void CharSprite_SetAlphaRate(int handle, int rate)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
sprite_set_blend_rate(&sp->sp, rate);
|
|
}
|
|
|
|
void CharSprite_SetShow(int handle, bool show)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
sprite_set_show(&sp->sp, show);
|
|
}
|
|
|
|
void CharSprite_GetChar(int handle, struct string **ch)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
if (*ch)
|
|
free_string(*ch);
|
|
if (sp->ch)
|
|
*ch = string_ref(sp->ch);
|
|
else
|
|
*ch = string_ref(&EMPTY_STRING);
|
|
}
|
|
|
|
int CharSprite_GetAlphaRate(int handle)
|
|
{
|
|
struct charsprite *sp = charsprite_get(handle);
|
|
return sprite_get_blend_rate(&sp->sp);
|
|
}
|