459 lines
16 KiB
C
459 lines
16 KiB
C
|
|
/**
|
||
|
|
* @file world.c
|
||
|
|
* @brief The content pipeline: sprites, characters, the town map, and what the
|
||
|
|
* map's actors do once they exist.
|
||
|
|
*
|
||
|
|
* Almost nothing here is game logic. It is the four steps between a directory
|
||
|
|
* of JSON and a world with people standing in it, in the one order that works,
|
||
|
|
* plus the two hooks -- a `movementlogicfunc` and a `renderfunc` -- that a game
|
||
|
|
* has to supply because the library does not.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <limits.h>
|
||
|
|
#include <math.h>
|
||
|
|
#include <stdbool.h>
|
||
|
|
|
||
|
|
#include <SDL3/SDL.h>
|
||
|
|
#include <akerror.h>
|
||
|
|
#include <akstdlib.h>
|
||
|
|
|
||
|
|
#include <akgl/actor.h>
|
||
|
|
#include <akgl/character.h>
|
||
|
|
#include <akgl/error.h>
|
||
|
|
#include <akgl/game.h>
|
||
|
|
#include <akgl/heap.h>
|
||
|
|
#include <akgl/physics.h>
|
||
|
|
#include <akgl/registry.h>
|
||
|
|
#include <akgl/sprite.h>
|
||
|
|
#include <akgl/tilemap.h>
|
||
|
|
|
||
|
|
#include "jrpg.h"
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The three character templates and the twenty-four sprites that dress them.
|
||
|
|
* The file names are a product of these three tables rather than a list of
|
||
|
|
* twenty-four strings, because that is what the naming convention *is*: one
|
||
|
|
* sprite per character, per motion, per facing. A missing combination shows up
|
||
|
|
* as a load failure naming the file, not as art that silently never appears.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* Character template Sprite name prefix */
|
||
|
|
static char *CAST[] = {
|
||
|
|
"player", /* jrpg_player jrpg_player_* */
|
||
|
|
"elder", /* jrpg_elder jrpg_elder_* */
|
||
|
|
"shopkeeper" /* jrpg_shopkeeper jrpg_shopkeeper_* */
|
||
|
|
};
|
||
|
|
|
||
|
|
static char *MOTIONS[] = { "idle", "walk" };
|
||
|
|
static char *FACINGS[] = { "up", "down", "left", "right" };
|
||
|
|
|
||
|
|
#define CAST_COUNT (sizeof(CAST) / sizeof(CAST[0]))
|
||
|
|
#define MOTION_COUNT (sizeof(MOTIONS) / sizeof(MOTIONS[0]))
|
||
|
|
#define FACING_COUNT (sizeof(FACINGS) / sizeof(FACINGS[0]))
|
||
|
|
|
||
|
|
/** @brief One person in the town who has something to say. */
|
||
|
|
typedef struct {
|
||
|
|
char *actor; /**< Registry key, which is the `name` of the map object that spawned them. */
|
||
|
|
char *line; /**< What they say. */
|
||
|
|
} jrpg_Townsfolk;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Actor (the map object's name) What they say
|
||
|
|
*/
|
||
|
|
static jrpg_Townsfolk TOWNSFOLK[] = {
|
||
|
|
{ "elder", "ELDER: The old road north is closed. Nothing to be done about it,\nand nothing beyond it worth the walk." },
|
||
|
|
{ "shopkeeper", "SHOPKEEPER: Nothing in stock but tiles and good intentions.\nCome back when I have inventory." }
|
||
|
|
};
|
||
|
|
|
||
|
|
#define TOWNSFOLK_COUNT (sizeof(TOWNSFOLK) / sizeof(TOWNSFOLK[0]))
|
||
|
|
|
||
|
|
/** @brief How close the player has to stand, in pixels between sprite centres. */
|
||
|
|
#define TALK_RANGE 64.0f
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The player's footprint, in pixels from the top-left of a 32x32 frame. A
|
||
|
|
* character stands on the bottom third of their sprite, so that is the part
|
||
|
|
* that has to fit through a doorway -- testing the whole frame would make every
|
||
|
|
* corridor two tiles wide.
|
||
|
|
*
|
||
|
|
* 0 32
|
||
|
|
* +------------------------------+ 0
|
||
|
|
* | |
|
||
|
|
* | (art) |
|
||
|
|
* | |
|
||
|
|
* | +----------------+ | 20 FEET_TOP
|
||
|
|
* | | footprint | |
|
||
|
|
* +------+----------------+------+ 30 FEET_TOP + FEET_H
|
||
|
|
* 6 26
|
||
|
|
* FEET_X FEET_X + FEET_W
|
||
|
|
*/
|
||
|
|
#define FEET_X 6.0f
|
||
|
|
#define FEET_W 20.0f
|
||
|
|
#define FEET_TOP 20.0f
|
||
|
|
#define FEET_H 10.0f
|
||
|
|
|
||
|
|
/** @brief Where the party member walks, in pixels relative to the player. */
|
||
|
|
#define FOLLOWER_OFFSET_X (-14.0f)
|
||
|
|
#define FOLLOWER_OFFSET_Y (10.0f)
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Load one sprite definition by its three naming components.
|
||
|
|
*
|
||
|
|
* @param cast Character stem, e.g. `"player"`.
|
||
|
|
* @param motion `"idle"` or `"walk"`.
|
||
|
|
* @param facing `"up"`, `"down"`, `"left"` or `"right"`.
|
||
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext *sprite_load(char *cast, char *motion, char *facing)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
char path[PATH_MAX];
|
||
|
|
int written = 0;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, cast, AKERR_NULLPOINTER, "cast");
|
||
|
|
FAIL_ZERO_RETURN(errctx, motion, AKERR_NULLPOINTER, "motion");
|
||
|
|
FAIL_ZERO_RETURN(errctx, facing, AKERR_NULLPOINTER, "facing");
|
||
|
|
|
||
|
|
PASS(errctx,
|
||
|
|
aksl_snprintf(
|
||
|
|
&written,
|
||
|
|
path,
|
||
|
|
sizeof(path),
|
||
|
|
"%s/sprite_jrpg_%s_%s_%s.json",
|
||
|
|
JRPG_ASSET_DIR,
|
||
|
|
cast,
|
||
|
|
motion,
|
||
|
|
facing
|
||
|
|
));
|
||
|
|
PASS(errctx, akgl_sprite_load_json(path));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Load one character definition by its stem.
|
||
|
|
* @param cast Character stem, e.g. `"player"`.
|
||
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext *character_load(char *cast)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
char path[PATH_MAX];
|
||
|
|
int written = 0;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, cast, AKERR_NULLPOINTER, "cast");
|
||
|
|
PASS(errctx,
|
||
|
|
aksl_snprintf(
|
||
|
|
&written,
|
||
|
|
path,
|
||
|
|
sizeof(path),
|
||
|
|
"%s/character_jrpg_%s.json",
|
||
|
|
JRPG_ASSET_DIR,
|
||
|
|
cast
|
||
|
|
));
|
||
|
|
PASS(errctx, akgl_character_load_json(path));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_world_load(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
char path[PATH_MAX];
|
||
|
|
int written = 0;
|
||
|
|
size_t c = 0;
|
||
|
|
size_t m = 0;
|
||
|
|
size_t f = 0;
|
||
|
|
|
||
|
|
// Sprites first. A character JSON names its sprites by registry key and
|
||
|
|
// akgl_character_load_json refuses one it cannot find, so loading the two
|
||
|
|
// the other way round fails on every mapping in the file.
|
||
|
|
for ( c = 0; c < CAST_COUNT; c++ ) {
|
||
|
|
for ( m = 0; m < MOTION_COUNT; m++ ) {
|
||
|
|
for ( f = 0; f < FACING_COUNT; f++ ) {
|
||
|
|
PASS(errctx, sprite_load(CAST[c], MOTIONS[m], FACINGS[f]));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for ( c = 0; c < CAST_COUNT; c++ ) {
|
||
|
|
PASS(errctx, character_load(CAST[c]));
|
||
|
|
}
|
||
|
|
|
||
|
|
// And the map last: loading it creates an actor for every `actor` object in
|
||
|
|
// its object layer, and each of those resolves a character by name.
|
||
|
|
PASS(errctx,
|
||
|
|
aksl_snprintf(&written, path, sizeof(path), "%s/town.tmj", JRPG_ASSET_DIR));
|
||
|
|
PASS(errctx, akgl_tilemap_load(path, akgl_gamemap));
|
||
|
|
|
||
|
|
// The map declares its own physics -- `physics.model` and zero gravity on
|
||
|
|
// both axes -- and akgl_tilemap_load builds a backend for it and sets
|
||
|
|
// use_own_physics. It does not *switch* to it: akgl_game_update simulates
|
||
|
|
// through the global akgl_physics, whatever that points at. Honouring the
|
||
|
|
// map is the caller's job, and this is it.
|
||
|
|
if ( akgl_gamemap->use_own_physics ) {
|
||
|
|
akgl_physics = &akgl_gamemap->physics;
|
||
|
|
}
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_world_populate(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akgl_Actor *player = NULL;
|
||
|
|
akgl_Actor *follower = NULL;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
// Every actor the map spawned, including the player.
|
||
|
|
//
|
||
|
|
// akgl_actor_initialize sets movement_controls_face, and the default
|
||
|
|
// facefunc it installs clears every facing bit and then sets the one
|
||
|
|
// matching a *movement* bit. An NPC has no movement bits, so on the first
|
||
|
|
// frame its state falls from ALIVE|FACE_DOWN (17) to ALIVE (16), which is
|
||
|
|
// a state combination no character JSON maps a sprite to -- and an actor
|
||
|
|
// with no sprite for its state is skipped rather than reported. Every NPC
|
||
|
|
// in the town disappears on frame one, silently, and so does the player as
|
||
|
|
// soon as they stop walking.
|
||
|
|
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||
|
|
if ( akgl_heap_actors[i].refcount == 0 ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
akgl_heap_actors[i].movement_controls_face = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
player = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
|
||
|
|
FAIL_ZERO_RETURN(
|
||
|
|
errctx,
|
||
|
|
player,
|
||
|
|
AKGL_ERR_REGISTRY,
|
||
|
|
"town.tmj spawned no actor named \"player\""
|
||
|
|
);
|
||
|
|
player->movementlogicfunc = &jrpg_actor_logic_walk;
|
||
|
|
|
||
|
|
// The party member. Nothing in the map creates it, and nothing has to: an
|
||
|
|
// actor is a pool object with a name, and the character it instantiates is
|
||
|
|
// already registered. This one borrows the elder's template, which is the
|
||
|
|
// whole point of splitting instance from template.
|
||
|
|
PASS(errctx, akgl_heap_next_actor(&follower));
|
||
|
|
PASS(errctx, akgl_actor_initialize(follower, JRPG_FOLLOWER_NAME));
|
||
|
|
PASS(errctx, akgl_actor_set_character(follower, "jrpg_elder"));
|
||
|
|
follower->state = (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_DOWN);
|
||
|
|
follower->movement_controls_face = false;
|
||
|
|
follower->visible = true;
|
||
|
|
follower->layer = player->layer;
|
||
|
|
follower->renderfunc = &jrpg_follower_render;
|
||
|
|
PASS(errctx, player->addchild(player, follower));
|
||
|
|
|
||
|
|
// A child's velocity fields are read as a fixed offset from the parent
|
||
|
|
// rather than as a velocity: akgl_physics_simulate snaps a child to
|
||
|
|
// `parent->x + vx` and never simulates it. Set after addchild, because
|
||
|
|
// addchild is what makes them mean an offset.
|
||
|
|
follower->vx = FOLLOWER_OFFSET_X;
|
||
|
|
follower->vy = FOLLOWER_OFFSET_Y;
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Is the map cell containing this pixel one the player cannot enter?
|
||
|
|
*
|
||
|
|
* Two rules, and both of them are the game's rather than the library's. The
|
||
|
|
* outer ring of the map is solid, because nothing else stops an actor walking
|
||
|
|
* off the edge of the world. Everything else is the decoration layer: a cell
|
||
|
|
* with a tile in it is a building, a tree or a lamp post, and a cell with 0 in
|
||
|
|
* it is open ground.
|
||
|
|
*
|
||
|
|
* @param px Position in map pixels along x.
|
||
|
|
* @param py Position in map pixels along y.
|
||
|
|
* @return True when the cell blocks movement.
|
||
|
|
*/
|
||
|
|
static bool cell_solid(float32_t px, float32_t py)
|
||
|
|
{
|
||
|
|
int tx = 0;
|
||
|
|
int ty = 0;
|
||
|
|
|
||
|
|
tx = (int)(px / (float32_t)akgl_gamemap->tilewidth);
|
||
|
|
ty = (int)(py / (float32_t)akgl_gamemap->tileheight);
|
||
|
|
|
||
|
|
if ( (tx <= 0) || (ty <= 0) ||
|
||
|
|
(tx >= (akgl_gamemap->width - 1)) || (ty >= (akgl_gamemap->height - 1)) ) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return (akgl_gamemap->layers[JRPG_LAYER_SOLID].data[(ty * akgl_gamemap->width) + tx] != 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Would an actor whose frame is at (x, y) have its feet in a wall?
|
||
|
|
*
|
||
|
|
* Tests the four corners of the footprint. Four corners is enough only because
|
||
|
|
* the footprint is smaller than a tile in both directions; a bigger one would
|
||
|
|
* step over a single-cell wall between two of its corners.
|
||
|
|
*
|
||
|
|
* @param x Candidate frame position along x, in map pixels.
|
||
|
|
* @param y Candidate frame position along y, in map pixels.
|
||
|
|
* @return True when any corner of the footprint lands in a solid cell.
|
||
|
|
*/
|
||
|
|
static bool feet_blocked(float32_t x, float32_t y)
|
||
|
|
{
|
||
|
|
float32_t left = x + FEET_X;
|
||
|
|
float32_t right = x + FEET_X + FEET_W;
|
||
|
|
float32_t top = y + FEET_TOP;
|
||
|
|
float32_t bottom = y + FEET_TOP + FEET_H;
|
||
|
|
|
||
|
|
return (cell_solid(left, top) ||
|
||
|
|
cell_solid(right, top) ||
|
||
|
|
cell_solid(left, bottom) ||
|
||
|
|
cell_solid(right, bottom));
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_actor_logic_walk(akgl_Actor *obj, float32_t dt)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
|
||
|
|
FAIL_ZERO_RETURN(errctx, obj->basechar, AKERR_NULLPOINTER, "obj->basechar");
|
||
|
|
|
||
|
|
// A conversation freezes the world. AKGL_ERR_LOGICINTERRUPT raised from a
|
||
|
|
// movementlogicfunc means "skip the rest of this tick for this actor", and
|
||
|
|
// akgl_physics_simulate swallows it in a HANDLE block -- so gravity, drag,
|
||
|
|
// the velocity recompute and the move are all skipped and the frame carries
|
||
|
|
// on. Thrust has already been integrated by the time this runs, so it is
|
||
|
|
// zeroed here; leaving it would let it accumulate while frozen and lurch on
|
||
|
|
// the first step after the box closes. The movement bits go too, so the
|
||
|
|
// walk animation stops rather than marching on the spot -- which does mean
|
||
|
|
// a direction held across the close has to be pressed again.
|
||
|
|
if ( jrpg_textbox_showing() ) {
|
||
|
|
obj->tx = 0.0f;
|
||
|
|
obj->ty = 0.0f;
|
||
|
|
AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_ALL);
|
||
|
|
FAIL_RETURN(
|
||
|
|
errctx,
|
||
|
|
AKGL_ERR_LOGICINTERRUPT,
|
||
|
|
"%s does not move while a conversation is open",
|
||
|
|
(char *)obj->name
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Everything the default hook does -- re-copy the character's speed limits,
|
||
|
|
// turn the movement bits into signed acceleration -- is still wanted. This
|
||
|
|
// hook adds to it rather than replacing it.
|
||
|
|
PASS(errctx, akgl_actor_logic_movement(obj, dt));
|
||
|
|
|
||
|
|
// Where this step would put us. akgl_physics_simulate has already
|
||
|
|
// integrated thrust for this step and capped it against the character's
|
||
|
|
// speed ellipse, and it computes velocity as `e + t` -- and with the town's
|
||
|
|
// zero gravity and zero drag, `e` stays zero, so `v` is `t`. That is what
|
||
|
|
// makes the prediction exact rather than approximate. A map with gravity
|
||
|
|
// would have to account for `ey` here as well.
|
||
|
|
//
|
||
|
|
// Axis at a time, so walking into a wall diagonally slides along it rather
|
||
|
|
// than stopping dead.
|
||
|
|
if ( feet_blocked(obj->x + (obj->tx * dt), obj->y) ) {
|
||
|
|
obj->tx = 0.0f;
|
||
|
|
obj->ax = 0.0f;
|
||
|
|
}
|
||
|
|
if ( feet_blocked(obj->x, obj->y + (obj->ty * dt)) ) {
|
||
|
|
obj->ty = 0.0f;
|
||
|
|
obj->ay = 0.0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_follower_render(akgl_Actor *obj)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akgl_Actor *parent = NULL;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
|
||
|
|
|
||
|
|
// The guard. akgl_physics_simulate has already written this actor's x and y
|
||
|
|
// as `parent->x + vx` and `parent->y + vy`, which is an absolute world
|
||
|
|
// position; akgl_actor_render adds the parent's position to it a second
|
||
|
|
// time. Detaching the parent for the duration of the draw takes the branch
|
||
|
|
// that does not. CLEANUP puts it back on every path, including the failing
|
||
|
|
// one -- an actor left with a NULL parent would be simulated as a free
|
||
|
|
// agent on the very next step.
|
||
|
|
parent = obj->parent;
|
||
|
|
obj->parent = NULL;
|
||
|
|
ATTEMPT {
|
||
|
|
CATCH(errctx, akgl_actor_render(obj));
|
||
|
|
} CLEANUP {
|
||
|
|
obj->parent = parent;
|
||
|
|
} PROCESS(errctx) {
|
||
|
|
} FINISH(errctx, true);
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_camera_follow(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akgl_Actor *player = NULL;
|
||
|
|
float32_t mapw = 0.0f;
|
||
|
|
float32_t maph = 0.0f;
|
||
|
|
|
||
|
|
player = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
|
||
|
|
FAIL_ZERO_RETURN(errctx, player, AKGL_ERR_REGISTRY, "No actor named \"player\"");
|
||
|
|
|
||
|
|
mapw = (float32_t)(akgl_gamemap->width * akgl_gamemap->tilewidth);
|
||
|
|
maph = (float32_t)(akgl_gamemap->height * akgl_gamemap->tileheight);
|
||
|
|
|
||
|
|
akgl_camera->x = (player->x + 16.0f) - (akgl_camera->w / 2.0f);
|
||
|
|
akgl_camera->y = (player->y + 16.0f) - (akgl_camera->h / 2.0f);
|
||
|
|
|
||
|
|
if ( akgl_camera->x < 0.0f ) {
|
||
|
|
akgl_camera->x = 0.0f;
|
||
|
|
}
|
||
|
|
if ( akgl_camera->y < 0.0f ) {
|
||
|
|
akgl_camera->y = 0.0f;
|
||
|
|
}
|
||
|
|
if ( akgl_camera->x > (mapw - akgl_camera->w) ) {
|
||
|
|
akgl_camera->x = mapw - akgl_camera->w;
|
||
|
|
}
|
||
|
|
if ( akgl_camera->y > (maph - akgl_camera->h) ) {
|
||
|
|
akgl_camera->y = maph - akgl_camera->h;
|
||
|
|
}
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_cmhf_talk(akgl_Actor *obj, SDL_Event *event)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akgl_Actor *npc = NULL;
|
||
|
|
size_t i = 0;
|
||
|
|
float32_t dx = 0.0f;
|
||
|
|
float32_t dy = 0.0f;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
|
||
|
|
FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "event");
|
||
|
|
|
||
|
|
if ( jrpg_textbox_showing() ) {
|
||
|
|
jrpg_textbox_close();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
for ( i = 0; i < TOWNSFOLK_COUNT; i++ ) {
|
||
|
|
npc = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, TOWNSFOLK[i].actor, NULL);
|
||
|
|
if ( npc == NULL ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
dx = npc->x - obj->x;
|
||
|
|
dy = npc->y - obj->y;
|
||
|
|
if ( sqrtf((dx * dx) + (dy * dy)) > TALK_RANGE ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
PASS(errctx, jrpg_textbox_open(TOWNSFOLK[i].line));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_cmhf_ignore(akgl_Actor *obj, SDL_Event *event)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "obj");
|
||
|
|
FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "event");
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|