/*
* Copyright (c) 2001-2015 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
int mapSmoothScaling = 0;
extern AG_ObjectClass mapActorClass;
void
MAP_InitSubsystem(void)
{
AG_RegisterNamespace("MAP", "MAP_", "http://libagar.org/");
AG_RegisterClass(&mapClass);
AG_RegisterClass(&mapActorClass);
AG_RegisterClass(&mapEditorClass);
AG_RegisterClass(&mapEditorPseudoClass);
AG_RegisterClass(&mapViewClass);
mapIcon_Init();
}
void
MAP_DestroySubsystem(void)
{
AG_UnregisterClass(&mapClass);
AG_UnregisterClass(&mapActorClass);
AG_UnregisterClass(&mapEditorClass);
AG_UnregisterClass(&mapEditorPseudoClass);
AG_UnregisterClass(&mapViewClass);
}
void
MAP_NodeInit(MAP_Node *node)
{
TAILQ_INIT(&node->nrefs);
}
void
MAP_NodeDestroy(MAP *m, MAP_Node *node)
{
MAP_Item *r, *nr;
for (r = TAILQ_FIRST(&node->nrefs);
r != TAILQ_END(&node->nrefs);
r = nr) {
nr = TAILQ_NEXT(r, nrefs);
MAP_ItemDestroy(m, r);
free(r);
}
}
void
MAP_ItemInit(MAP_Item *r, enum map_item_type type)
{
r->type = type;
r->flags = 0;
r->layer = 0;
r->friction = 0;
r->p = NULL;
r->r_gfx.xcenter = 0;
r->r_gfx.ycenter = 0;
r->r_gfx.xmotion = 0;
r->r_gfx.ymotion = 0;
r->r_gfx.xorigin = 0;
r->r_gfx.yorigin = 0;
switch (type) {
case MAP_ITEM_TILE:
r->r_tile.obj = NULL;
r->r_tile.id = 0;
r->r_gfx.rs.x = 0;
r->r_gfx.rs.y = 0;
r->r_gfx.rs.w = 0;
r->r_gfx.rs.h = 0;
break;
case MAP_ITEM_ANIM:
r->r_anim.obj = NULL;
r->r_anim.id = 0;
break;
case MAP_ITEM_WARP:
r->r_warp.map = NULL;
r->r_warp.x = 0;
r->r_warp.y = 0;
r->r_warp.dir = 0;
break;
}
RG_TransformChainInit(&r->transforms);
TAILQ_INIT(&r->masks);
}
/*
* Adjust the centering offset of a given node reference.
* The parent map, if any, must be locked.
*/
void
MAP_ItemSetCenter(MAP_Item *r, int xcenter, int ycenter)
{
r->r_gfx.xcenter = (Sint16)xcenter;
r->r_gfx.ycenter = (Sint16)ycenter;
}
/*
* Adjust the motion offset of a given node reference.
* The parent map, if any, must be locked.
*/
void
MAP_ItemSetMotion(MAP_Item *r, int xmotion, int ymotion)
{
r->r_gfx.xmotion = (Sint16)xmotion;
r->r_gfx.ymotion = (Sint16)ymotion;
}
/*
* Define the coefficient of friction/acceleration for a given node reference.
* The parent map, if any, must be locked.
*/
void
MAP_ItemSetFriction(MAP_Item *r, int coeff)
{
r->friction = (Sint8)coeff;
}
/* Set the layer attribute of a noderef. */
void
MAP_ItemSetLayer(MAP_Item *r, int layer)
{
r->layer = layer;
}
void
MAP_ItemDestroy(MAP *m, MAP_Item *r)
{
MAP_NodeMask *mask, *mask_next;
RG_TransformChainDestroy(&r->transforms);
for (mask = TAILQ_FIRST(&r->masks);
mask != TAILQ_END(&r->masks);
mask = mask_next) {
mask_next = TAILQ_NEXT(mask, masks);
MAP_NodeMaskDestroy(m, mask);
}
switch (r->type) {
case MAP_ITEM_TILE:
if (r->r_tile.obj != NULL) {
AG_ObjectDelDep(m, r->r_tile.obj);
AG_ObjectPageOut(r->r_tile.obj);
}
break;
case MAP_ITEM_ANIM:
if (r->r_anim.obj != NULL) {
AG_ObjectDelDep(m, r->r_anim.obj);
AG_ObjectPageOut(r->r_anim.obj);
}
break;
case MAP_ITEM_WARP:
free(r->r_warp.map);
break;
default:
break;
}
}
void
MAP_ItemAttrColor(Uint flag, int state, AG_Color *c)
{
switch (flag) {
case MAP_ITEM_BLOCK:
if (state) {
AG_ColorRGBA_8(c, 255,0,0,64);
} else {
AG_ColorRGBA_8(c, 0,255,0,32);
}
break;
case MAP_ITEM_CLIMBABLE:
if (state) {
AG_ColorRGBA_8(c, 255,255,0,64);
} else {
AG_ColorRGBA_8(c, 255,0,0,32);
}
break;
case MAP_ITEM_SLIPPERY:
if (state) {
AG_ColorRGBA_8(c, 0,0,255,64);
} else {
AG_ColorRGBA_8(c, 0,0,0,0);
}
break;
case MAP_ITEM_JUMPABLE:
if (state) {
AG_ColorRGBA_8(c, 255,0,255,64);
} else {
AG_ColorRGBA_8(c, 0,0,0,0);
}
break;
}
}
/* Allocate and initialize the node map. */
int
MAP_AllocNodes(MAP *m, Uint w, Uint h)
{
Uint x, y;
if (w > MAP_WIDTH_MAX || h > MAP_HEIGHT_MAX) {
AG_SetError(_("%ux%u nodes exceed %ux%u."), w, h,
MAP_WIDTH_MAX, MAP_HEIGHT_MAX);
return (-1);
}
AG_ObjectLock(m);
m->mapw = w;
m->maph = h;
m->map = Malloc(h*sizeof(MAP_Node *));
for (y = 0; y < h; y++) {
m->map[y] = Malloc(w*sizeof(MAP_Node));
for (x = 0; x < w; x++) {
MAP_NodeInit(&m->map[y][x]);
}
}
AG_ObjectUnlock(m);
return (0);
}
/* Release the node map. */
void
MAP_FreeNodes(MAP *m)
{
Uint x, y;
MAP_Node *node;
AG_ObjectLock(m);
if (m->map != NULL) {
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
node = &m->map[y][x];
MAP_NodeDestroy(m, node);
}
free(m->map[y]);
}
free(m->map);
m->map = NULL;
}
AG_ObjectUnlock(m);
}
static void
FreeLayers(MAP *_Nonnull m)
{
m->layers = Realloc(m->layers, 1*sizeof(MAP_Layer));
m->nlayers = 1;
MAP_InitLayer(&m->layers[0], _("Layer 0"));
}
static void
FreeCameras(MAP *_Nonnull m)
{
m->cameras = Realloc(m->cameras , 1*sizeof(MAP_Camera));
m->ncameras = 1;
MAP_InitCamera(&m->cameras[0], _("Camera 0"));
}
/* Resize a map, initializing new nodes and destroying any excess ones. */
int
MAP_Resize(MAP *m, Uint w, Uint h)
{
MAP tm;
Uint x, y;
if (w > MAP_WIDTH_MAX || h > MAP_HEIGHT_MAX) {
AG_SetError(_("%ux%u nodes exceed %ux%u."), w, h,
MAP_WIDTH_MAX, MAP_HEIGHT_MAX);
return (-1);
}
AG_ObjectLock(m);
/* Save the nodes to a temporary map, to preserve dependencies. */
AG_ObjectInitStatic(&tm, &mapClass);
if (MAP_AllocNodes(&tm, m->mapw, m->maph) == -1) {
goto fail;
}
for (y = 0;
y < m->maph && y < h;
y++) {
for (x = 0;
x < m->mapw && x < w;
x++) {
MAP_NodeCopy(m, &m->map[y][x], -1, &tm, &tm.map[y][x],
-1);
}
}
/* Resize the map, restore the original nodes. */
MAP_FreeNodes(m);
if (MAP_AllocNodes(m, w, h) == -1) {
goto fail;
}
for (y = 0;
y < tm.maph && y < m->maph;
y++) {
for (x = 0;
x < tm.mapw && x < m->mapw;
x++) {
MAP_NodeCopy(&tm, &tm.map[y][x], -1, m, &m->map[y][x],
-1);
}
}
if (m->origin.x >= (int)w) { m->origin.x = (int)w-1; }
if (m->origin.y >= (int)h) { m->origin.y = (int)h-1; }
AG_ObjectUnlock(m);
AG_ObjectDestroy(&tm);
return (0);
fail:
AG_ObjectUnlock(m);
AG_ObjectDestroy(&tm);
return (-1);
}
/* Set the display scaling factor. */
void
MAP_SetZoom(MAP *m, int ncam, Uint zoom)
{
MAP_Camera *cam = &m->cameras[ncam];
AG_ObjectLock(m);
cam->zoom = zoom;
if ((cam->tilesz = cam->zoom*MAPTILESZ/100) > MAP_TILESZ_MAX) {
cam->tilesz = MAP_TILESZ_MAX;
}
AG_ObjectUnlock(m);
}
MAP *
MAP_New(void *parent, const char *name)
{
MAP *m;
m = Malloc(sizeof(MAP));
AG_ObjectInit(m, &mapClass);
AG_ObjectSetNameS(m, name);
AG_ObjectAttach(parent, m);
return (m);
}
void
MAP_InitLayer(MAP_Layer *lay, const char *name)
{
Strlcpy(lay->name, name, sizeof(lay->name));
lay->visible = 1;
lay->xinc = 1;
lay->yinc = 1;
lay->alpha = 255;
}
void
MAP_InitCamera(MAP_Camera *cam, const char *name)
{
Strlcpy(cam->name, name, sizeof(cam->name));
cam->x = 0;
cam->y = 0;
cam->flags = 0;
cam->alignment = AG_MAP_CENTER;
cam->zoom = 100;
cam->tilesz = MAPTILESZ;
cam->pixsz = 1;
}
int
MAP_AddCamera(MAP *m, const char *name)
{
m->cameras = Realloc(m->cameras,
(m->ncameras+1)*sizeof(MAP_Camera));
MAP_InitCamera(&m->cameras[m->ncameras], name);
return (m->ncameras++);
}
/* Initialize undo block. */
static void
InitModBlk(MAP_ModBlk *_Nonnull blk)
{
blk->mods = Malloc(sizeof(MAP_Mod));
blk->nmods = 0;
blk->cancel = 0;
}
/* Destroy undo block. */
static void
FreeModBlk(MAP *_Nonnull m, MAP_ModBlk *_Nonnull blk)
{
Uint i;
for (i = 0; i < blk->nmods; i++) {
MAP_Mod *mm = &blk->mods[i];
switch (mm->type) {
case AG_MAPMOD_NODECHG:
MAP_NodeDestroy(m, &mm->mm_nodechg.node);
break;
default:
break;
}
}
free(blk->mods);
}
void
MAP_InitModBlks(MAP *m)
{
m->blks = Malloc(sizeof(MAP_ModBlk));
m->nblks = 1;
InitModBlk(&m->blks[0]);
m->curblk = 0;
m->nmods = 0;
MAP_ModBegin(m);
}
static void
Init(void *_Nonnull obj)
{
extern int mapEditorInited; /* mapedit.c */
extern int mapDefaultWidth;
extern int mapDefaultHeight;
MAP *m = obj;
m->flags = 0;
m->redraw = 0;
m->mapw = 0;
m->maph = 0;
m->origin.x = 0;
m->origin.y = 0;
m->origin.layer = 0;
m->map = NULL;
m->cur_layer = 0;
m->layers = Malloc(sizeof(MAP_Layer));
m->nlayers = 1;
m->cameras = Malloc(sizeof(MAP_Camera));
m->ncameras = 1;
TAILQ_INIT(&m->actors);
MAP_InitLayer(&m->layers[0], _("Layer 0"));
MAP_InitCamera(&m->cameras[0], _("Camera 0"));
MAP_InitModBlks(m);
if (!mapEditorInited) {
mapEditorInited = 1;
MAP_EditorInit();
}
MAP_AllocNodes(m, mapDefaultWidth, mapDefaultHeight);
m->origin.x = mapDefaultWidth/2;
m->origin.y = mapDefaultHeight/2;
}
/* Create a new layer. */
int
MAP_PushLayer(MAP *m, const char *name)
{
char layname[MAP_LAYER_NAME_MAX];
if (name[0] == '\0') {
Snprintf(layname, sizeof(layname), _("Layer %u"), m->nlayers);
} else {
Strlcpy(layname, name, sizeof(layname));
}
if (m->nlayers+1 > MAP_LAYERS_MAX) {
AG_SetError(_("Too many layers."));
return (-1);
}
m->layers = Realloc(m->layers, (m->nlayers+1)*sizeof(MAP_Layer));
MAP_InitLayer(&m->layers[m->nlayers], layname);
m->nlayers++;
return (0);
}
/* Remove the last layer. */
void
MAP_PopLayer(MAP *m)
{
if (--m->nlayers < 1)
m->nlayers = 1;
}
/* Set or change the tile reference of a TILE item. */
void
MAP_ItemSetTile(MAP_Item *r, MAP *map, RG_Tileset *ts, Uint tile_id)
{
RG_Tile *tile;
if (r->r_tile.obj != ts && r->r_tile.obj != NULL) {
AG_ObjectDelDep(map, r->r_tile.obj);
AG_ObjectPageOut(r->r_tile.obj);
} else {
AG_ObjectAddDep(map, ts, 1);
if (AG_ObjectPageIn(ts) == -1)
AG_FatalError(NULL);
}
r->r_tile.obj = ts;
r->r_tile.id = tile_id;
r->r_gfx.rs.x = 0;
r->r_gfx.rs.y = 0;
if (ts != NULL &&
RG_LookupTile(ts, tile_id, &tile) == 0 &&
tile->su != NULL) {
r->r_gfx.rs.w = tile->su->w;
r->r_gfx.rs.h = tile->su->h;
}
}
/*
* Create a tile reference item.
* The map must be locked.
*/
MAP_Item *
MAP_NodeAddTile(MAP *map, MAP_Node *node, RG_Tileset *ts, Uint32 tileid)
{
MAP_Item *r;
r = Malloc(sizeof(MAP_Item));
MAP_ItemInit(r, MAP_ITEM_TILE);
MAP_ItemSetTile(r, map, ts, tileid);
TAILQ_INSERT_TAIL(&node->nrefs, r, nrefs);
return (r);
}
/*
* Create a tile animation reference item.
* The map must be locked.
*/
void
MAP_ItemSetAnim(MAP_Item *r, MAP *map, RG_Tileset *ts, Uint anim_id)
{
RG_Anim *anim;
if (r->r_anim.obj != NULL) {
AG_ObjectDelDep(map, r->r_anim.obj);
AG_ObjectPageOut(r->r_anim.obj);
}
if (ts != NULL) {
AG_ObjectAddDep(map, ts, 1);
if (AG_ObjectPageIn(ts) == -1)
AG_FatalError(NULL);
}
r->r_anim.obj = ts;
r->r_anim.id = anim_id;
r->r_gfx.rs.x = 0;
r->r_gfx.rs.y = 0;
r->r_gfx.rs.w = 0;
r->r_gfx.rs.h = 0;
if (ts != NULL &&
RG_LookupAnim(ts, anim_id, &anim) == 0) {
r->r_gfx.rs.w = anim->w;
r->r_gfx.rs.h = anim->h;
}
}
/*
* Insert a reference to an animation.
* The map must be locked.
*/
MAP_Item *
MAP_NodeAddAnim(MAP *map, MAP_Node *node, RG_Tileset *ts, Uint32 animid)
{
MAP_Item *r;
r = Malloc(sizeof(MAP_Item));
MAP_ItemInit(r, MAP_ITEM_ANIM);
MAP_ItemSetAnim(r, map, ts, animid);
TAILQ_INSERT_TAIL(&node->nrefs, r, nrefs);
return (r);
}
/*
* Insert a reference to a location on another map.
* The map must be locked.
*/
MAP_Item *
MAP_NodeAddWarpPoint(MAP *map, MAP_Node *node, const char *mapname,
int x, int y, Uint8 dir)
{
MAP_Item *r;
r = Malloc(sizeof(MAP_Item));
MAP_ItemInit(r, MAP_ITEM_WARP);
r->r_warp.map = Strdup(mapname);
r->r_warp.x = x;
r->r_warp.y = y;
r->r_warp.dir = dir;
TAILQ_INSERT_TAIL(&node->nrefs, r, nrefs);
return (r);
}
/*
* Move a reference to a specified node and optionally assign to a
* specified layer.
*/
void
MAP_NodeMoveItem(MAP *sm, MAP_Node *sn, MAP_Item *r, MAP *dm, MAP_Node *dn,
int dlayer)
{
AG_ObjectLock(sm);
AG_ObjectLock(dm);
TAILQ_REMOVE(&sn->nrefs, r, nrefs);
TAILQ_INSERT_TAIL(&dn->nrefs, r, nrefs);
if (dlayer != -1)
r->layer = dlayer;
switch (r->type) {
case MAP_ITEM_TILE:
AG_ObjectDelDep(sm, r->r_tile.obj);
AG_ObjectAddDep(dm, r->r_tile.obj, 1);
break;
case MAP_ITEM_ANIM:
AG_ObjectDelDep(sm, r->r_anim.obj);
AG_ObjectAddDep(dm, r->r_anim.obj, 1);
break;
default:
break;
}
AG_ObjectUnlock(dm);
AG_ObjectUnlock(sm);
}
/*
* Copy references from source node sn which are associated with slayer (or
* all references if slayer is -1) to destination node dn, and associate
* the copy with dlayer (or the original layer, if dlayer is -1).
*/
void
MAP_NodeCopy(MAP *sm, MAP_Node *sn, int slayer, MAP *dm, MAP_Node *dn,
int dlayer)
{
MAP_Item *sr;
AG_ObjectLock(sm);
AG_ObjectLock(dm);
TAILQ_FOREACH(sr, &sn->nrefs, nrefs) {
if (slayer != -1 &&
sr->layer != slayer) {
continue;
}
MAP_NodeCopyItem(sr, dm, dn, dlayer);
}
AG_ObjectUnlock(dm);
AG_ObjectUnlock(sm);
}
/*
* Copy a node reference from one node to another.
* Both the source and destination maps must be locked.
*/
MAP_Item *
MAP_NodeCopyItem(const MAP_Item *sr, MAP *dm, MAP_Node *dn, int dlayer)
{
MAP_NodeMask *mask;
MAP_Item *dr = NULL;
RG_Transform *xf, *xfDup;
/* Allocate a new noderef with the same data. */
switch (sr->type) {
case MAP_ITEM_TILE:
dr = MAP_NodeAddTile(dm, dn, sr->r_tile.obj, sr->r_tile.id);
dr->r_gfx.xcenter = sr->r_gfx.xcenter;
dr->r_gfx.ycenter = sr->r_gfx.ycenter;
dr->r_gfx.xmotion = sr->r_gfx.xmotion;
dr->r_gfx.ymotion = sr->r_gfx.ymotion;
dr->r_gfx.xorigin = sr->r_gfx.xorigin;
dr->r_gfx.yorigin = sr->r_gfx.yorigin;
memcpy(&dr->r_gfx.rs, &sr->r_gfx.rs, sizeof(AG_Rect));
break;
case MAP_ITEM_ANIM:
dr = MAP_NodeAddAnim(dm, dn, sr->r_anim.obj, sr->r_anim.id);
dr->r_gfx.xcenter = sr->r_gfx.xcenter;
dr->r_gfx.ycenter = sr->r_gfx.ycenter;
dr->r_gfx.xmotion = sr->r_gfx.xmotion;
dr->r_gfx.ymotion = sr->r_gfx.ymotion;
dr->r_gfx.xorigin = sr->r_gfx.xorigin;
dr->r_gfx.yorigin = sr->r_gfx.yorigin;
memcpy(&dr->r_gfx.rs, &sr->r_gfx.rs, sizeof(AG_Rect));
break;
case MAP_ITEM_WARP:
dr = MAP_NodeAddWarpPoint(dm, dn, sr->r_warp.map, sr->r_warp.x,
sr->r_warp.y, sr->r_warp.dir);
break;
default:
AG_FatalError("Bad node type");
}
dr->flags = sr->flags;
dr->layer = (dlayer == -1) ? sr->layer : dlayer;
dr->friction = sr->friction;
/* Inherit the transformations. */
TAILQ_FOREACH(xf, &sr->transforms, transforms) {
xfDup = Malloc(sizeof(RG_Transform));
RG_TransformInit(xfDup , xf->type, xf->nargs, xf->args);
TAILQ_INSERT_TAIL(&dr->transforms, xfDup, transforms);
}
/* Inherit the node masks. */
TAILQ_FOREACH(mask, &sr->masks, masks) {
MAP_NodeMask *nmask;
nmask = Malloc(sizeof(MAP_NodeMask));
MAP_NodeMaskInit(nmask, mask->type);
MAP_NodeMaskCopy(mask, dm, nmask);
TAILQ_INSERT_TAIL(&dr->masks, nmask, masks);
}
return (dr);
}
/* Remove a noderef from a node and free it. */
void
MAP_NodeDelItem(MAP *m, MAP_Node *node, MAP_Item *r)
{
AG_ObjectLock(m);
TAILQ_REMOVE(&node->nrefs, r, nrefs);
MAP_ItemDestroy(m, r);
AG_ObjectUnlock(m);
free(r);
}
/* Remove all references associated with the given layer. */
void
MAP_NodeRemoveAll(MAP *m, MAP_Node *node, int layer)
{
MAP_Item *r, *nr;
AG_ObjectLock(m);
for (r = TAILQ_FIRST(&node->nrefs);
r != TAILQ_END(&node->nrefs);
r = nr) {
nr = TAILQ_NEXT(r, nrefs);
if (layer != -1 &&
layer != r->layer) {
continue;
}
TAILQ_REMOVE(&node->nrefs, r, nrefs);
MAP_ItemDestroy(m, r);
free(r);
}
AG_ObjectUnlock(m);
}
/* Move all references from a layer to another. */
void
MAP_NodeSwapLayers(MAP *m, MAP_Node *node, int layer1, int layer2)
{
MAP_Item *r;
AG_ObjectLock(m);
TAILQ_FOREACH(r, &node->nrefs, nrefs) {
if (r->layer == layer1)
r->layer = layer2;
}
AG_ObjectUnlock(m);
}
/*
* Move a noderef to the upper layer.
* The map containing the node must be locked.
*/
void
MAP_NodeMoveItemUp(MAP_Node *node, MAP_Item *r)
{
MAP_Item *next = TAILQ_NEXT(r, nrefs);
if (next != NULL) {
TAILQ_REMOVE(&node->nrefs, r, nrefs);
TAILQ_INSERT_AFTER(&node->nrefs, next, r, nrefs);
}
}
/*
* Move a noderef to the lower layer.
* The map containing the node must be locked.
*/
void
MAP_NodeMoveItemDown(MAP_Node *node, MAP_Item *r)
{
MAP_Item *prev = TAILQ_PREV(r, map_itemq, nrefs);
if (prev != NULL) {
TAILQ_REMOVE(&node->nrefs, r, nrefs);
TAILQ_INSERT_BEFORE(prev, r, nrefs);
}
}
/*
* Move a noderef to the tail of the queue.
* The map containing the node must be locked.
*/
void
MAP_NodeMoveItemToTail(MAP_Node *node, MAP_Item *r)
{
if (r != TAILQ_LAST(&node->nrefs, map_itemq)) {
TAILQ_REMOVE(&node->nrefs, r, nrefs);
TAILQ_INSERT_TAIL(&node->nrefs, r, nrefs);
}
}
/*
* Move a noderef to the head of the queue.
* The map containing the node must be locked.
*/
void
MAP_NodeMoveItemToHead(MAP_Node *node, MAP_Item *r)
{
if (r != TAILQ_FIRST(&node->nrefs)) {
TAILQ_REMOVE(&node->nrefs, r, nrefs);
TAILQ_INSERT_HEAD(&node->nrefs, r, nrefs);
}
}
static void
Reset(void *_Nonnull p)
{
MAP *m = p;
Uint i;
if (m->map != NULL)
MAP_FreeNodes(m);
if (m->layers != NULL)
FreeLayers(m);
if (m->cameras != NULL)
FreeCameras(m);
for (i = 0; i < m->nblks; i++) {
FreeModBlk(m, &m->blks[i]);
}
free(m->blks);
MAP_InitModBlks(m);
}
static void
Destroy(void *_Nonnull p)
{
MAP *m = p;
free(m->layers);
free(m->cameras);
}
/*
* Load a node reference.
* The map must be locked.
*/
int
MAP_ItemLoad(MAP *m, AG_DataSource *ds, MAP_Node *node, MAP_Item **r)
{
enum map_item_type type;
Uint32 nmasks = 0;
Uint8 flags;
Uint8 layer;
Sint8 friction;
Uint32 obj_ref, offs;
void *pobj;
Uint i;
/* Read the type of reference, flags and the layer#. */
type = (enum map_item_type)AG_ReadUint32(ds);
flags = (Uint)AG_ReadUint32(ds);
layer = AG_ReadUint8(ds);
friction = AG_ReadSint8(ds);
/* Read the reference data. */
switch (type) {
case MAP_ITEM_TILE:
{
obj_ref = AG_ReadUint32(ds);
offs = AG_ReadUint32(ds);
if (AG_ObjectFindDep(m, obj_ref, &pobj) == -1) {
return (-1);
}
*r = MAP_NodeAddTile(m, node, pobj, offs);
(*r)->flags = flags;
(*r)->layer = layer;
(*r)->friction = friction;
(*r)->r_gfx.xcenter = AG_ReadSint16(ds);
(*r)->r_gfx.ycenter = AG_ReadSint16(ds);
(*r)->r_gfx.xmotion = AG_ReadSint16(ds);
(*r)->r_gfx.ymotion = AG_ReadSint16(ds);
(*r)->r_gfx.xorigin = AG_ReadSint16(ds);
(*r)->r_gfx.yorigin = AG_ReadSint16(ds);
(*r)->r_gfx.rs.x = AG_ReadSint16(ds);
(*r)->r_gfx.rs.y = AG_ReadSint16(ds);
(*r)->r_gfx.rs.w = AG_ReadUint16(ds);
(*r)->r_gfx.rs.h = AG_ReadUint16(ds);
}
break;
case MAP_ITEM_ANIM:
{
obj_ref = AG_ReadUint32(ds);
offs = AG_ReadUint32(ds);
if (AG_ObjectFindDep(m, obj_ref, &pobj) == -1) {
return (-1);
}
*r = MAP_NodeAddAnim(m, node, pobj, offs);
(*r)->flags = flags;
(*r)->layer = layer;
(*r)->friction = friction;
(*r)->r_gfx.xcenter = AG_ReadSint16(ds);
(*r)->r_gfx.ycenter = AG_ReadSint16(ds);
(*r)->r_gfx.xmotion = AG_ReadSint16(ds);
(*r)->r_gfx.ymotion = AG_ReadSint16(ds);
(*r)->r_gfx.xorigin = AG_ReadSint16(ds);
(*r)->r_gfx.yorigin = AG_ReadSint16(ds);
(*r)->r_gfx.rs.x = AG_ReadSint16(ds);
(*r)->r_gfx.rs.y = AG_ReadSint16(ds);
(*r)->r_gfx.rs.w = AG_ReadUint16(ds);
(*r)->r_gfx.rs.h = AG_ReadUint16(ds);
}
break;
case MAP_ITEM_WARP:
{
char map_id[AG_OBJECT_NAME_MAX];
Uint32 ox, oy;
Uint8 dir;
if (AG_CopyString(map_id, ds, sizeof(map_id)) >=
sizeof(map_id)) {
AG_SetError(_("Warp map name is too big."));
return (-1);
}
ox = AG_ReadUint32(ds);
oy = AG_ReadUint32(ds);
if (ox > MAP_WIDTH_MAX || oy > MAP_HEIGHT_MAX) {
AG_SetError(_("Invalid warp coordinates."));
return (-1);
}
dir = AG_ReadUint8(ds);
*r = MAP_NodeAddWarpPoint(m, node, map_id, ox, oy, dir);
(*r)->flags = flags;
(*r)->layer = layer;
(*r)->friction = friction;
}
break;
default:
AG_SetError("Unknown map item type: %d", type);
return (-1);
}
/* Read the transform chain. */
if (RG_TransformChainLoad(ds, &(*r)->transforms) == -1)
goto fail;
/* Read the node masks. */
if ((nmasks = AG_ReadUint32(ds)) > MAP_ITEM_MAXMASKS) {
AG_SetError("Too many node masks: %u", (Uint)nmasks);
goto fail;
}
for (i = 0; i < nmasks; i++) {
MAP_NodeMask *mask;
mask = Malloc(sizeof(MAP_NodeMask));
MAP_NodeMaskInit(mask, 0);
if (MAP_NodeMaskLoad(m, ds, mask) == -1) {
free(mask);
goto fail;
}
TAILQ_INSERT_TAIL(&(*r)->masks, mask, masks);
}
return (0);
fail:
if (*r != NULL) {
MAP_ItemDestroy(m, *r);
free(*r);
*r = NULL;
}
return (-1);
}
int
MAP_NodeLoad(MAP *m, AG_DataSource *ds, MAP_Node *node)
{
Uint32 nrefs;
MAP_Item *r;
Uint i;
if ((nrefs = AG_ReadUint32(ds)) > MAP_NODE_ITEMS_MAX) {
AG_SetError(_("Too many noderefs."));
return (-1);
}
for (i = 0; i < nrefs; i++) {
if (MAP_ItemLoad(m, ds, node, &r) == -1) {
MAP_NodeDestroy(m, node);
MAP_NodeInit(node);
return (-1);
}
}
return (0);
}
void
MAP_AttachActor(MAP *m, MAP_Actor *a)
{
AG_ObjectLock(a);
if (a->g_map.x < 0 || a->g_map.x >= (int)m->mapw ||
a->g_map.y < 0 || a->g_map.y >= (int)m->maph) {
fprintf(stderr,
"Illegal coordinates: %s:%d,%d; not attaching %s\n",
OBJECT(m)->name, a->g_map.x, a->g_map.y,
OBJECT(a)->name);
AG_ObjectUnlock(a);
return;
}
AG_ObjectAddDep(m, a, 1);
a->type = AG_ACTOR_MAP;
a->parent = m;
a->g_map.x0 = a->g_map.x;
a->g_map.y0 = a->g_map.y;
a->g_map.x1 = a->g_map.x;
a->g_map.y1 = a->g_map.y;
if (MAP_ACTOR_OPS(a)->map != NULL) {
MAP_ACTOR_OPS(a)->map(a, m);
}
AG_ObjectUnlock(a);
}
void
MAP_DetachActor(MAP *m, MAP_Actor *a)
{
AG_ObjectLock(a);
if (AG_OfClass(m, "MAP:*")) {
MAP_ActorUnmapTile(a);
}
AG_ObjectDelDep(m, a);
a->parent = NULL;
AG_ObjectUnlock(a);
}
static int
Load(void *_Nonnull ob, AG_DataSource *_Nonnull ds, const AG_Version *_Nonnull ver)
{
MAP *m = ob;
Uint32 w, h, origin_x, origin_y;
Uint i, x, y;
MAP_Actor *a;
m->flags = (Uint)AG_ReadUint32(ds) & AG_MAP_SAVED_FLAGS;
w = AG_ReadUint32(ds);
h = AG_ReadUint32(ds);
origin_x = AG_ReadUint32(ds);
origin_y = AG_ReadUint32(ds);
if (w > MAP_WIDTH_MAX || h > MAP_HEIGHT_MAX ||
origin_x > MAP_WIDTH_MAX || origin_y > MAP_HEIGHT_MAX) {
AG_SetError(_("Invalid map geometry."));
goto fail;
}
m->mapw = (Uint)w;
m->maph = (Uint)h;
m->origin.x = (int)origin_x;
m->origin.y = (int)origin_y;
/* Read the layer information. */
if ((m->nlayers = (Uint)AG_ReadUint32(ds)) > MAP_LAYERS_MAX) {
AG_SetError(_("Too many layers."));
goto fail;
}
if (m->nlayers < 1) {
AG_SetError(_("Missing zeroth layer."));
goto fail;
}
m->layers = Realloc(m->layers, m->nlayers*sizeof(MAP_Layer));
for (i = 0; i < m->nlayers; i++) {
MAP_Layer *lay = &m->layers[i];
AG_CopyString(lay->name, ds, sizeof(lay->name));
lay->visible = (int)AG_ReadUint8(ds);
lay->xinc = AG_ReadSint16(ds);
lay->yinc = AG_ReadSint16(ds);
lay->alpha = AG_ReadUint8(ds);
}
m->cur_layer = (int)AG_ReadUint8(ds);
m->origin.layer = (int)AG_ReadUint8(ds);
/* Read the camera information. */
if ((m->ncameras = (Uint)AG_ReadUint32(ds)) > MAP_CAMERAS_MAX) {
AG_SetError(_("Too many cameras."));
goto fail;
}
if (m->ncameras < 1) {
AG_SetError(_("Missing zeroth camera."));
goto fail;
}
m->cameras = Realloc(m->cameras, m->ncameras*sizeof(MAP_Camera));
for (i = 0; i < m->ncameras; i++) {
MAP_Camera *cam = &m->cameras[i];
AG_CopyString(cam->name, ds, sizeof(cam->name));
cam->flags = (int)AG_ReadUint32(ds);
if (i > 0 || m->flags & AG_MAP_SAVE_CAM0POS) {
cam->x = (int)AG_ReadSint32(ds);
cam->y = (int)AG_ReadSint32(ds);
}
cam->alignment =
(enum map_camera_alignment)AG_ReadUint8(ds);
if (i > 0 || m->flags & AG_MAP_SAVE_CAM0ZOOM) {
cam->zoom = (Uint)AG_ReadUint16(ds);
cam->tilesz = (Uint)AG_ReadUint16(ds);
cam->pixsz = cam->tilesz/MAPTILESZ;
} else {
cam->zoom = 100;
cam->tilesz = MAPTILESZ;
cam->pixsz = 1;
}
if (i == 0 && (m->flags & AG_MAP_SAVE_CAM0POS) == 0) {
cam->x = m->origin.x*cam->tilesz - cam->tilesz/2;
cam->y = m->origin.y*cam->tilesz - cam->tilesz/2;
}
}
/* Allocate and load the nodes. */
if (MAP_AllocNodes(m, m->mapw, m->maph) == -1) {
goto fail;
}
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
if (MAP_NodeLoad(m, ds, &m->map[y][x]) == -1)
goto fail;
}
}
/* Attach the actor objects. */
TAILQ_FOREACH(a, &m->actors, actors) {
Debug(m, "Attaching actor %s at %d,%d,%d\n",
OBJECT(a)->name, a->g_map.x, a->g_map.y,
a->g_map.l0);
Debug(a, "Attached to map %s at %d,%d,%d\n",
OBJECT(m)->name, a->g_map.x, a->g_map.y,
a->g_map.l0);
MAP_AttachActor(m, a);
}
return (0);
fail:
return (-1);
}
/*
* Save a node reference.
* The noderef's parent map must be locked.
*/
void
MAP_ItemSave(MAP *m, AG_DataSource *ds, MAP_Item *r)
{
off_t nmasks_offs;
Uint32 nmasks = 0;
MAP_NodeMask *mask;
/* Save the type of reference, flags and layer information. */
AG_WriteUint32(ds, (Uint32)r->type);
AG_WriteUint32(ds, (Uint32)r->flags);
AG_WriteUint8(ds, r->layer);
AG_WriteSint8(ds, r->friction);
/* Save the reference. */
switch (r->type) {
case MAP_ITEM_TILE:
AG_WriteUint32(ds, AG_ObjectEncodeName(m, r->r_tile.obj));
AG_WriteUint32(ds, r->r_tile.id);
break;
case MAP_ITEM_ANIM:
AG_WriteUint32(ds, AG_ObjectEncodeName(m, r->r_anim.obj));
AG_WriteUint32(ds, r->r_anim.id);
break;
case MAP_ITEM_WARP:
AG_WriteString(ds, r->r_warp.map);
AG_WriteUint32(ds, (Uint32)r->r_warp.x);
AG_WriteUint32(ds, (Uint32)r->r_warp.y);
AG_WriteUint8(ds, r->r_warp.dir);
break;
default:
break;
}
if (r->type == MAP_ITEM_TILE || r->type == MAP_ITEM_ANIM) {
AG_WriteSint16(ds, r->r_gfx.xcenter);
AG_WriteSint16(ds, r->r_gfx.ycenter);
AG_WriteSint16(ds, r->r_gfx.xmotion);
AG_WriteSint16(ds, r->r_gfx.ymotion);
AG_WriteSint16(ds, r->r_gfx.xorigin);
AG_WriteSint16(ds, r->r_gfx.yorigin);
AG_WriteSint16(ds, r->r_gfx.rs.x);
AG_WriteSint16(ds, r->r_gfx.rs.y);
AG_WriteUint16(ds, r->r_gfx.rs.w);
AG_WriteUint16(ds, r->r_gfx.rs.h);
}
/* Save the transform chain. */
RG_TransformChainSave(ds, &r->transforms);
/* Save the masks. */
nmasks_offs = AG_Tell(ds);
AG_WriteUint32(ds, 0);
TAILQ_FOREACH(mask, &r->masks, masks) {
MAP_NodeMaskSave(m, ds, mask);
nmasks++;
}
AG_WriteUint32At(ds, nmasks, nmasks_offs);
}
void
MAP_NodeSave(MAP *m, AG_DataSource *ds, MAP_Node *node)
{
MAP_Item *r;
off_t nrefs_offs;
Uint32 nrefs = 0;
nrefs_offs = AG_Tell(ds);
AG_WriteUint32(ds, 0);
TAILQ_FOREACH(r, &node->nrefs, nrefs) {
if (r->flags & MAP_ITEM_NOSAVE) {
continue;
}
MAP_ItemSave(m, ds, r);
nrefs++;
}
AG_WriteUint32At(ds, nrefs, nrefs_offs);
}
static int
Save(void *_Nonnull p, AG_DataSource *_Nonnull ds)
{
MAP *m = p;
Uint i, x, y;
AG_WriteUint32(ds, (Uint32)(m->flags & AG_MAP_SAVED_FLAGS));
AG_WriteUint32(ds, (Uint32)m->mapw);
AG_WriteUint32(ds, (Uint32)m->maph);
AG_WriteUint32(ds, (Uint32)m->origin.x);
AG_WriteUint32(ds, (Uint32)m->origin.y);
/* Write the layer information. */
AG_WriteUint32(ds, (Uint32)m->nlayers);
for (i = 0; i < m->nlayers; i++) {
MAP_Layer *lay = &m->layers[i];
AG_WriteString(ds, lay->name);
AG_WriteUint8(ds, (Uint8)lay->visible);
AG_WriteSint16(ds, lay->xinc);
AG_WriteSint16(ds, lay->yinc);
AG_WriteUint8(ds, lay->alpha);
}
AG_WriteUint8(ds, m->cur_layer);
AG_WriteUint8(ds, m->origin.layer);
/* Write the camera information. */
AG_WriteUint32(ds, (Uint32)m->ncameras);
for (i = 0; i < m->ncameras; i++) {
MAP_Camera *cam = &m->cameras[i];
AG_WriteString(ds, cam->name);
AG_WriteUint32(ds, (Uint32)cam->flags);
if (i == 0 && (m->flags & AG_MAP_SAVE_CAM0POS)) {
AG_WriteSint32(ds, (Sint32)cam->x);
AG_WriteSint32(ds, (Sint32)cam->y);
}
AG_WriteUint8(ds, (Uint8)cam->alignment);
if (i == 0 && (m->flags & AG_MAP_SAVE_CAM0ZOOM)) {
AG_WriteUint16(ds, (Uint16)cam->zoom);
AG_WriteUint16(ds, (Uint16)cam->tilesz);
}
}
/* Write the nodes. */
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++)
MAP_NodeSave(m, ds, &m->map[y][x]);
}
return (0);
}
/* XXX 1.4 */
#if 0
/* Render surface s, scaled to rx,ry pixels. */
static void
BlitSurfaceScaled(MAP *_Nonnull m, AG_Surface *_Nonnull s, AG_Rect *_Nonnull rs,
int rx, int ry, int cam)
{
int x, y, sx, sy;
Uint8 r1, g1, b1, a1;
Uint32 c;
int tilesz = m->cameras[cam].tilesz;
int xSrc = (int)(rs->x*tilesz/MAPTILESZ);
int ySrc = (int)(rs->y*tilesz/MAPTILESZ);
int wSrc = (int)(rs->w*tilesz/MAPTILESZ);
int hSrc = (int)(rs->h*tilesz/MAPTILESZ);
AG_SurfaceLock(s);
AG_SurfaceLock(agView->v);
for (y = 0; y < hSrc; y++) {
if ((sy = (y+ySrc)*MAPTILESZ/tilesz) >= s->h)
break;
for (x = 0; x < wSrc; x++) {
if ((sx = (x+xSrc)*MAPTILESZ/tilesz) >= s->w)
break;
c = AG_GET_PIXEL(s, (Uint8 *)s->pixels +
sy*s->pitch +
sx*s->format->BytesPerPixel);
if ((s->flags & AG_SRCCOLORKEY) &&
c == s->format->colorkey)
continue;
if (s->flags & AG_SRCALPHA) {
AG_GetRGBA(c, s->format, &r1,&g1,&b1,&a1);
AG_BLEND_RGBA2_CLIPPED(agView->v, rx+x, ry+y,
r1, g1, b1, a1, AG_ALPHA_OVERLAY);
} else {
AG_GetRGB(c, s->format, &r1,&g1,&b1);
AG_PutPixel()
AG_VIEW_PUT_PIXEL2_CLIPPED(rx+x, ry+y,
AG_MapRGB(agVideoFmt, r1,g1,b1));
}
}
}
AG_SurfaceUnlock(s);
AG_SurfaceUnlock(agView->v);
}
#endif
static MAP_Item *_Nullable
LocateItem(MAP *_Nonnull m, MAP_Node *_Nonnull node, int xoffs, int yoffs,
int xd, int yd, int ncam)
{
AG_Rect rExt;
MAP_Item *r;
TAILQ_FOREACH_REVERSE(r, &node->nrefs, map_itemq, nrefs) {
if (r->layer != m->cur_layer) {
continue;
}
switch (r->type) {
case MAP_ITEM_TILE:
if (MAP_ItemExtent(m, r, &rExt, ncam) == 0 &&
xoffs+xd >= rExt.x && xoffs+xd < rExt.x+rExt.w &&
yoffs+yd >= rExt.y && yoffs+yd < rExt.y+rExt.h) {
return (r);
}
break;
default:
break;
}
}
return (NULL);
}
/* Locate a map item. */
MAP_Item *
MAP_ItemLocate(MAP *m, int xMap, int yMap, int ncam)
{
MAP_Camera *cam = &m->cameras[ncam];
int x = xMap/cam->tilesz;
int y = yMap/cam->tilesz;
int xoffs = xMap%cam->tilesz;
int yoffs = yMap%cam->tilesz;
MAP_Item *r;
if (x < 0 || y < 0 || x >= (int)m->mapw || x >= (int)m->maph) {
return (NULL);
}
if ((r = LocateItem(m, &m->map[y][x], xoffs, yoffs, 0, 0, ncam))
!= NULL) {
return (r);
}
if (y+1 < (int)m->maph) {
if ((r = LocateItem(m, &m->map[y+1][x], xoffs, yoffs,
0, -cam->tilesz, ncam)) != NULL) {
return (r);
}
}
if (y-1 >= 0) {
if ((r = LocateItem(m, &m->map[y-1][x], xoffs, yoffs,
0, +cam->tilesz, ncam)) != NULL) {
return (r);
}
}
if (x+1 < (int)m->mapw) {
if ((r = LocateItem(m, &m->map[y][x+1], xoffs, yoffs,
-cam->tilesz, 0, ncam)) != NULL) {
return (r);
}
}
if (x-1 >= 0) {
if ((r = LocateItem(m, &m->map[y][x-1], xoffs, yoffs,
+cam->tilesz, 0, ncam)) != NULL) {
return (r);
}
}
/* Check diagonal nodes. */
if (x+1 < (int)m->mapw && y+1 < (int)m->maph) {
if ((r = LocateItem(m, &m->map[y+1][x+1], xoffs, yoffs,
-cam->tilesz, -cam->tilesz, ncam)) != NULL) {
return (r);
}
}
if (x-1 >= 0 && y-1 >= 0) {
if ((r = LocateItem(m, &m->map[y-1][x-1], xoffs, yoffs,
+cam->tilesz, +cam->tilesz, ncam)) != NULL) {
return (r);
}
}
if (x-1 >= 0 && y+1 < (int)m->maph) {
if ((r = LocateItem(m, &m->map[y+1][x-1], xoffs, yoffs,
+cam->tilesz, -cam->tilesz, ncam)) != NULL) {
return (r);
}
}
if (x+1 < (int)m->mapw && y-1 >= 0) {
if ((r = LocateItem(m, &m->map[y-1][x+1], xoffs, yoffs,
-cam->tilesz, +cam->tilesz, ncam)) != NULL) {
return (r);
}
}
return (NULL);
}
/*
* Return the dimensions of a graphical item, and coordinates relative to
* the origin of the the node.
*/
int
MAP_ItemExtent(MAP *m, MAP_Item *r, AG_Rect *rd, int cam)
{
int tilesz = m->cameras[cam].tilesz;
if ((r->type == MAP_ITEM_TILE &&
RG_LookupTile(r->r_tile.obj, r->r_tile.id, NULL) == -1) ||
(r->type == MAP_ITEM_ANIM &&
RG_LookupAnim(r->r_anim.obj, r->r_anim.id, NULL) == -1)) {
return (-1);
}
if (tilesz != MAPTILESZ) {
rd->x = r->r_gfx.xcenter*tilesz/MAPTILESZ +
r->r_gfx.xmotion*tilesz/MAPTILESZ -
r->r_gfx.xorigin*tilesz/MAPTILESZ;
rd->y = r->r_gfx.ycenter*tilesz/MAPTILESZ +
r->r_gfx.ymotion*tilesz/MAPTILESZ -
r->r_gfx.yorigin*tilesz/MAPTILESZ;
rd->w = r->r_gfx.rs.w*tilesz/MAPTILESZ;
rd->h = r->r_gfx.rs.h*tilesz/MAPTILESZ;
} else {
rd->x = r->r_gfx.xcenter + r->r_gfx.xmotion - r->r_gfx.xorigin;
rd->y = r->r_gfx.ycenter + r->r_gfx.ymotion - r->r_gfx.yorigin;
rd->w = r->r_gfx.rs.w;
rd->h = r->r_gfx.rs.h;
}
return (0);
}
void
MAP_ItemDraw(MAP *m, MAP_Item *r, int x, int y, int cam)
{
/* XXX TODO */
}
/* Create a new undo block at the current level. */
void
MAP_ModBegin(MAP *m)
{
/* Destroy blocks at upper levels. */
while (m->nblks > m->curblk+1)
FreeModBlk(m, &m->blks[--m->nblks]);
m->blks = Realloc(m->blks, (++m->nblks)*sizeof(MAP_Mod));
InitModBlk(&m->blks[m->curblk++]);
}
void
MAP_ModCancel(MAP *m)
{
MAP_ModBlk *blk = &m->blks[m->curblk];
blk->cancel = 1;
}
void
MAP_ModEnd(MAP *m)
{
MAP_ModBlk *blk = &m->blks[m->curblk];
if (blk->nmods == 0 || blk->cancel == 1) {
FreeModBlk(m, blk);
m->nblks--;
m->curblk--;
}
}
void
MAP_Undo(MAP *m)
{
MAP_ModBlk *blk = &m->blks[m->curblk];
Uint i;
if (m->curblk-1 <= 0)
return;
for (i = 0; i < blk->nmods; i++) {
MAP_Mod *mm = &blk->mods[i];
switch (mm->type) {
case AG_MAPMOD_NODECHG:
{
MAP_Node *n = &m->map[mm->mm_nodechg.y]
[mm->mm_nodechg.x];
MAP_Item *r;
MAP_NodeRemoveAll(m, n, -1);
TAILQ_FOREACH(r, &mm->mm_nodechg.node.nrefs,
nrefs) {
MAP_NodeCopyItem(r, m, n, -1);
}
}
break;
default:
break;
}
}
FreeModBlk(m, blk);
m->nblks--;
m->curblk--;
}
void
MAP_Redo(MAP *m)
{
/* TODO */
}
void
MAP_ModNodeChg(MAP *m, int x, int y)
{
MAP_Node *node = &m->map[y][x];
MAP_ModBlk *blk = &m->blks[m->nblks-1];
MAP_Mod *mm;
MAP_Item *sr;
Uint i;
for (i = 0; i < blk->nmods; i++) {
mm = &blk->mods[i];
if (mm->type == AG_MAPMOD_NODECHG &&
mm->mm_nodechg.x == x &&
mm->mm_nodechg.y == y)
return;
}
blk->mods = Realloc(blk->mods, (blk->nmods+1)*sizeof(MAP_Mod));
mm = &blk->mods[blk->nmods++];
mm->type = AG_MAPMOD_NODECHG;
mm->mm_nodechg.x = x;
mm->mm_nodechg.y = y;
MAP_NodeInit(&mm->mm_nodechg.node);
TAILQ_FOREACH(sr, &node->nrefs, nrefs)
MAP_NodeCopyItem(sr, m, &mm->mm_nodechg.node, -1);
}
void
MAP_ModLayerAdd(MAP *m, int l)
{
}
#if 0
/* Break a surface into tile-sized fragments and generate a map. */
MAP *
AG_GenerateMapFromSurface(AG_Gfx *gfx, AG_Surface *sprite)
{
char mapname[AG_OBJECT_NAME_MAX];
int x, y, mx, my;
Uint mw, mh;
AG_Rect sd;
MAP *fragmap;
sd.w = MAPTILESZ;
sd.h = MAPTILESZ;
mw = sprite->w/MAPTILESZ + 1;
mh = sprite->h/MAPTILESZ + 1;
fragmap = Malloc(sizeof(MAP));
AG_ObjectInit(fragmap, &mapClass);
if (MAP_AllocNodes(fragmap, mw, mh) == -1)
AG_FatalError(NULL);
for (y = 0, my = 0; y < sprite->h; y += MAPTILESZ, my++) {
for (x = 0, mx = 0; x < sprite->w; x += MAPTILESZ, mx++) {
AG_Surface *su;
Uint32 saflags = sprite->flags & (AG_SRCALPHA);
Uint32 sckflags = sprite->flags & (AG_SRCCOLORKEY);
Uint8 salpha = sprite->format->alpha;
Uint32 scolorkey = sprite->format->colorkey;
MAP_Node *node = &fragmap->map[my][mx];
Uint32 nsprite;
int fw = MAPTILESZ;
int fh = MAPTILESZ;
if (sprite->w - x < MAPTILESZ)
fw = sprite->w - x;
if (sprite->h - y < MAPTILESZ)
fh = sprite->h - y;
/* Allocate a surface for the fragment. */
su = AG_SurfaceRGBA(fw, fh,
sprite->format->BitsPerPixel,
(sprite->flags &
(AG_SRCALPHA|AG_SRCCOLORKEY)),
sprite->format->Rmask,
sprite->format->Gmask,
sprite->format->Bmask,
sprite->format->Amask);
if (su == NULL) {
AG_FatalError(NULL);
}
/* Copy the fragment as-is. */
AG_SetAlpha(sprite, 0, 0);
AG_SetColorKey(sprite, 0, 0);
sd.x = x;
sd.y = y;
AG_SurfaceBlit(sprite, &sd, su, 0,0);
nsprite = AG_GfxAddTile(gfx, su);
AG_SetAlpha(sprite, saflags, salpha);
AG_SetColorKey(sprite, sckflags, scolorkey);
/*
* Enable alpha blending if there are any pixels
* with a non-opaque alpha channel on the surface.
*/
if (AG_HasTransparency(su))
AG_SetAlpha(su, AG_SRCALPHA, su->format->alpha);
/* Map the sprite as a NULL reference. */
MAP_NodeAddTile(fragmap, node, NULL, nsprite);
}
}
AG_GfxAddSubmap(gfx, fragmap);
return (fragmap);
}
#endif
/* Create a new map view. */
static void
CreateView(AG_Event *_Nonnull event)
{
MAP_View *omv = AG_PTR(1);
AG_Window *pwin = AG_PTR(2);
MAP *map = omv->map;
MAP_View *mv;
AG_Window *win;
win = AG_WindowNew(0);
AG_WindowSetCaption(win, _("View: %s"), OBJECT(map)->name);
mv = MAP_ViewNew(win, map, 0, NULL, NULL);
mv->cam = MAP_AddCamera(map, _("View"));
MAP_ViewSizeHint(mv, 2, 2);
AG_WidgetFocus(mv);
AG_WindowAttach(pwin, win);
AG_WindowSetGeometryAlignedPct(win, AG_WINDOW_MC, 60, 50);
AG_WindowShow(win);
}
static void
SelectTool(AG_Event *_Nonnull event)
{
MAP_View *mv = AG_PTR(1);
MAP_Tool *ntool = AG_PTR(2);
MAP_ViewSelectTool(mv, ntool, mv->map);
AG_WidgetFocus(mv);
}
static void
ResizeMap(AG_Event *_Nonnull event)
{
AG_MSpinbutton *msb = AG_SELF();
MAP *m = AG_PTR(1);
MAP_View *mv = AG_PTR(2);
MAP_Resize(m, msb->xvalue, msb->yvalue);
AG_PostEvent(NULL, mv, "map-resized", NULL);
}
/* Display the list of undo blocks. */
static void
PollUndoBlks(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_SELF();
MAP *m = AG_PTR(1);
Uint i, j;
AG_TlistClear(tl);
for (i = 0; i < m->nblks; i++) {
MAP_ModBlk *blk = &m->blks[i];
AG_TlistItem *it;
it = AG_TlistAdd(tl, NULL, "%sBlock %d (%d mods)",
i==m->curblk ? "*" : "", i, blk->nmods);
it->depth = 0;
for (j = 0; j < blk->nmods; j++) {
MAP_Mod *mod = &blk->mods[j];
switch (mod->type) {
case AG_MAPMOD_NODECHG:
it = AG_TlistAdd(tl, NULL, "NODECHG (%d,%d)",
mod->mm_nodechg.x, mod->mm_nodechg.y);
break;
case AG_MAPMOD_LAYERADD:
it = AG_TlistAdd(tl, NULL, "LAYERADD (%d)",
mod->mm_layeradd.nlayer);
break;
case AG_MAPMOD_LAYERDEL:
it = AG_TlistAdd(tl, NULL, "LAYERDEL (%d)",
mod->mm_layerdel.nlayer);
break;
}
it->depth = 1;
}
}
AG_TlistRestore(tl);
}
static void
EditMapParameters(AG_Event *_Nonnull event)
{
MAP_View *mv = AG_PTR(1);
MAP *m = mv->map;
AG_Window *pwin = AG_PTR(2);
AG_Window *win;
AG_MSpinbutton *msb;
AG_Notebook *nb;
AG_NotebookTab *ntab;
if ((win = AG_WindowNewNamed(0, "MAP_Edit-Parameters-%s",
OBJECT(m)->name)) == NULL) {
return;
}
AG_WindowSetCaption(win, _("Map parameters: <%s>"), OBJECT(m)->name);
AG_WindowSetPosition(win, AG_WINDOW_MIDDLE_LEFT, 0);
nb = AG_NotebookNew(win, AG_NOTEBOOK_HFILL|AG_NOTEBOOK_VFILL);
ntab = AG_NotebookAdd(nb, _("Map settings"), AG_BOX_VERT);
{
msb = AG_MSpinbuttonNew(ntab, 0, "x", _("Map size: "));
AG_MSpinbuttonSetRange(msb, 1, MAP_WIDTH_MAX);
msb->xvalue = m->mapw;
msb->yvalue = m->maph;
AG_SetEvent(msb, "mspinbutton-return",
ResizeMap, "%p,%p", m, mv);
msb = AG_MSpinbuttonNew(ntab, 0, ",", _("Origin position: "));
AG_BindInt(msb, "xvalue", &m->origin.x);
AG_BindInt(msb, "yvalue", &m->origin.y);
AG_MSpinbuttonSetRange(msb, 0, MAP_WIDTH_MAX);
AG_NumericalNewIntR(ntab, 0, NULL,
_("Origin layer: "), &m->origin.layer, 0, 255);
}
ntab = AG_NotebookAdd(nb, _("View"), AG_BOX_VERT);
{
msb = AG_MSpinbuttonNew(ntab, 0, ",", _("Node offset: "));
AG_BindInt(msb, "xvalue", &mv->mx);
AG_BindInt(msb, "yvalue", &mv->my);
AG_MSpinbuttonSetRange(msb,
-MAP_WIDTH_MAX/2, MAP_WIDTH_MAX/2);
/* XXX unsafe */
msb = AG_MSpinbuttonNew(ntab, 0, ",", _("Camera position: "));
AG_BindInt(msb, "xvalue", &AGMCAM(mv).x);
AG_BindInt(msb, "yvalue", &AGMCAM(mv).y);
AG_NumericalNewUintR(ntab, 0, NULL,
_("Zoom factor: "), &AGMCAM(mv).zoom, 1, 100);
AG_NumericalNewInt(ntab, 0, "px",
_("Tile size: "), &AGMTILESZ(mv));
msb = AG_MSpinbuttonNew(ntab, 0, ",",
_("Display offset (view): "));
AG_BindInt(msb, "xvalue", &mv->xoffs);
AG_BindInt(msb, "yvalue", &mv->yoffs);
AG_MSpinbuttonSetRange(msb, -MAP_TILESZ_MAX, MAP_TILESZ_MAX);
msb = AG_MSpinbuttonNew(ntab, 0, "x", _("Display area: "));
AG_BindUint(msb, "xvalue", &mv->mw);
AG_BindUint(msb, "yvalue", &mv->mh);
AG_MSpinbuttonSetRange(msb, 1, MAP_WIDTH_MAX);
AG_SeparatorNew(ntab, AG_SEPARATOR_HORIZ);
AG_CheckboxNewInt(ntab, 0, _("Smooth scaling"),
&mapSmoothScaling);
AG_SeparatorNew(ntab, AG_SEPARATOR_HORIZ);
AG_LabelNewPolled(ntab, AG_LABEL_HFILL,
_("Camera: %i"), &mv->cam);
#ifdef THREADS
AG_LabelNewPolledMT(ntab, AG_LABEL_HFILL, &OBJECT(m)->lock,
_("Current layer: %i"), &OBJECT(m)->lock, &m->cur_layer);
#else
AG_LabelNewPolled(ntab, AG_LABEL_HFILL,
_("Current layer: %i"), &m->cur_layer);
#endif
AG_LabelNewPolled(ntab, AG_LABEL_HFILL,
_("Cursor position: %ix%i"), &mv->cx, &mv->cy);
AG_LabelNewPolled(ntab, AG_LABEL_HFILL,
_("Mouse selection: %[ibool] (%i+%i,%i+%i)"), &mv->msel.set,
&mv->msel.x, &mv->msel.xoffs, &mv->msel.y, &mv->msel.yoffs);
AG_LabelNewPolled(ntab, AG_LABEL_HFILL,
_("Effective selection: %[ibool] (%ix%i at %i,%i)"),
&mv->esel.set,
&mv->esel.w, &mv->esel.h, &mv->esel.x, &mv->esel.y);
}
ntab = AG_NotebookAdd(nb, _("Undo"), AG_BOX_VERT);
{
AG_Tlist *tl;
tl = AG_TlistNew(ntab, AG_TLIST_POLL|AG_TLIST_TREE|
AG_TLIST_EXPAND);
WIDGET(tl)->flags |= AG_WIDGET_HFILL|AG_WIDGET_VFILL;
AG_SetEvent(tl, "tlist-poll", PollUndoBlks, "%p", mv->map);
}
AG_WindowAttach(pwin, win);
AG_WindowShow(win);
}
#if 0
/* Scan VFS for objects we can use as library items. */
static void
PollLibsFind(AG_Tlist *_Nonnull tl, AG_Object *_Nonnull pob, int depth)
{
AG_Object *cob;
AG_TlistItem *it;
it = AG_TlistAdd(tl, NULL, "%s%s", pob->name,
OBJECT_RESIDENT(pob) ? _(" (resident)") : "");
it->p1 = pob;
it->depth = depth;
if (AG_OfClass(pob, "RG_Tileset:*")) {
RG_Tileset *ts = (RG_Tileset *)pob;
AG_TlistItem *sit;
RG_Tile *tile;
it->cat = "tileset";
AG_MutexLock(&ts->lock);
if (!TAILQ_EMPTY(&ts->tiles)) {
it->flags |= AG_TLIST_HAS_CHILDREN;
}
if ((it->flags & AG_TLIST_HAS_CHILDREN) &&
AG_TlistVisibleChildren(tl, it)) {
TAILQ_FOREACH(tile, &ts->tiles, tiles) {
if (tile->su == NULL) {
continue;
}
sit = AG_TlistAdd(tl, NULL, "%s (%ux%u)",
tile->name, tile->su->w, tile->su->h);
sit->depth = depth+1;
sit->cat = "tile";
sit->p1 = tile;
AG_TlistSetIcon(tl, sit, tile->su);
}
}
AG_MutexUnlock(&ts->lock);
} else {
it->cat = "object";
}
if (!TAILQ_EMPTY(&pob->children)) {
it->flags |= AG_TLIST_HAS_CHILDREN;
if (AG_ObjectRoot(pob) == pob)
it->flags |= AG_TLIST_VISIBLE_CHILDREN;
}
if ((it->flags & AG_TLIST_HAS_CHILDREN) &&
AG_TlistVisibleChildren(tl, it)) {
TAILQ_FOREACH(cob, &pob->children, cobjs)
PollLibsFind(tl, cob, depth+1);
}
}
static void
PollLibs(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_SELF();
AG_Object *pob = AG_PTR(1);
AG_TlistClear(tl);
AG_LockLinkage();
PollLibsFind(tl, pob, 0);
AG_UnlockLinkage();
AG_TlistRestore(tl);
}
#endif
/* Select a library item. */
static void
SelectLib(AG_Event *_Nonnull event)
{
MAP_View *mv = AG_PTR(1);
AG_TlistItem *it = AG_PTR(2);
int state = AG_INT(3);
MAP_Tool *t;
if (state == 0) {
if (mv->curtool != NULL &&
(mv->curtool->ops == &mapInsertOps ||
mv->curtool->ops == &mapGInsertOps))
MAP_ViewSelectTool(mv, NULL, NULL);
} else {
if (strcmp(it->cat, "tile") == 0) {
RG_Tile *tile = it->p1;
if ((t = MAP_ViewFindTool(mv, "Insert")) != NULL) {
struct map_insert_tool *ins =
(struct map_insert_tool*)t;
ins->snap_mode = tile->snap_mode;
ins->replace_mode = (ins->snap_mode ==
RG_SNAP_TO_GRID);
if (mv->curtool != NULL) {
MAP_ViewSelectTool(mv, NULL, NULL);
}
MAP_ViewSelectTool(mv, t, mv->map);
AG_WidgetFocus(mv);
}
} else if (strcmp(it->cat, "object") == 0 &&
AG_OfClass(it->p1, "MAP_Actor:*")) {
if ((t = MAP_ViewFindTool(mv, "Ginsert")) != NULL) {
if (mv->curtool != NULL) {
MAP_ViewSelectTool(mv, NULL, NULL);
}
MAP_ViewSelectTool(mv, t, mv->map);
AG_WidgetFocus(mv);
}
} else {
MAP_ViewSelectTool(mv, NULL, NULL);
}
}
}
/* Scan VFS for items we can use as actors. */
static void
PollActors(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_SELF();
MAP_View *mv = AG_PTR(1);
MAP *m = mv->map;
AG_TlistItem *it;
MAP_Actor *a;
AG_TlistClear(tl);
TAILQ_FOREACH(a, &m->actors, actors) {
it = AG_TlistAdd(tl, NULL, "%s [%d,%d %d-%d]",
OBJECT(a)->name, a->g_map.x, a->g_map.y,
a->g_map.l0, a->g_map.l1);
it->p1 = a;
it->cat = "actor";
}
AG_TlistRestore(tl);
}
/* Display the current map layers. */
static void
PollLayers(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_SELF();
MAP *m = AG_PTR(1);
AG_TlistItem *it;
Uint i;
AG_TlistClear(tl);
for (i = 0; i < m->nlayers; i++) {
MAP_Layer *lay = &m->layers[i];
it = AG_TlistAdd(tl, mapIconLayerEditor.s, "%s%s%s",
(i == m->cur_layer) ? "[*] " : "", lay->name,
lay->visible ? "" : _(" (hidden)"));
it->p1 = lay;
it->cat = "layer";
}
AG_TlistRestore(tl);
}
static void
SetLayerVisibility(AG_Event *_Nonnull event)
{
MAP_Layer *lay = AG_PTR(1);
int state = AG_INT(2);
lay->visible = state;
}
/* Select a layer for edition. */
static void
SelectLayer(AG_Event *_Nonnull event)
{
MAP *m = AG_PTR(1);
AG_TlistItem *ti = AG_PTR(2);
MAP_Layer *layer = ti->p1;
Uint nlayer;
for (nlayer = 0; nlayer < m->nlayers; nlayer++) {
if (&m->layers[nlayer] == layer) {
m->cur_layer = nlayer;
break;
}
}
}
/*
* Delete a layer. Unless this is the top layer, we have to update
* the layer indices of all relevant items.
*/
static void
DeleteLayer(AG_Event *_Nonnull event)
{
MAP *m = AG_PTR(1);
MAP_Layer *lay = AG_PTR(2);
Uint i, x, y, nlayer;
for (nlayer = 0; nlayer < m->nlayers; nlayer++) {
if (&m->layers[nlayer] == lay)
break;
}
if (nlayer == m->nlayers) {
return;
}
if (--m->nlayers < 1) {
m->nlayers = 1;
return;
}
if (m->cur_layer <= (int)m->nlayers) {
m->cur_layer = (int)m->nlayers-1;
}
for (i = nlayer; i <= m->nlayers; i++) {
memcpy(&m->layers[i], &m->layers[i+1], sizeof(MAP_Layer));
}
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
MAP_Node *node = &m->map[y][x];
MAP_Item *r, *nr;
for (r = TAILQ_FIRST(&node->nrefs);
r != TAILQ_END(&node->nrefs);
r = nr) {
nr = TAILQ_NEXT(r, nrefs);
if (r->layer == nlayer) {
TAILQ_REMOVE(&node->nrefs, r, nrefs);
MAP_ItemDestroy(m, r);
free(r);
} else if (r->layer > nlayer) {
r->layer--;
}
}
}
}
}
/* Destroy all items on a given layer. */
static void
ClearLayer(AG_Event *_Nonnull event)
{
MAP *m = AG_PTR(1);
MAP_Layer *lay = AG_PTR(2);
Uint x, y, nlayer;
for (nlayer = 0; nlayer < m->nlayers; nlayer++) {
if (&m->layers[nlayer] == lay)
break;
}
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
MAP_Node *node = &m->map[y][x];
MAP_Item *r, *nr;
for (r = TAILQ_FIRST(&node->nrefs);
r != TAILQ_END(&node->nrefs);
r = nr) {
nr = TAILQ_NEXT(r, nrefs);
if (r->layer == nlayer) {
TAILQ_REMOVE(&node->nrefs, r, nrefs);
MAP_ItemDestroy(m, r);
free(r);
}
}
}
}
}
/* Move a layer (and its associated items) up or down the stack. */
static void
MoveLayer(AG_Event *_Nonnull event)
{
char tmp[MAP_LAYER_NAME_MAX];
MAP *m = AG_PTR(1);
MAP_Layer *lay1 = AG_PTR(2), *lay2;
int movedown = AG_INT(3);
AG_Tlist *tlLayers = AG_PTR(4);
Uint l1, l2;
Uint x, y;
for (l1 = 0; l1 < m->nlayers; l1++) {
if (&m->layers[l1] == lay1)
break;
}
if (l1 == m->nlayers) {
return;
}
if (movedown) {
l2 = l1+1;
if (l2 >= m->nlayers) return;
} else {
if (((int)l1 - 1) < 0) return;
l2 = l1-1;
}
lay1 = &m->layers[l1];
lay2 = &m->layers[l2];
Strlcpy(tmp, lay1->name, sizeof(tmp));
Strlcpy(lay1->name, lay2->name, sizeof(lay1->name));
Strlcpy(lay2->name, tmp, sizeof(lay2->name));
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
MAP_Node *node = &m->map[y][x];
MAP_Item *r;
TAILQ_FOREACH(r, &node->nrefs, nrefs) {
if (r->layer == l1) {
r->layer = l2;
} else if (r->layer == l2) {
r->layer = l1;
}
}
}
}
AG_TlistSelectPtr(tlLayers, lay2);
}
/* Create a new layer. */
static void
PushLayer(AG_Event *_Nonnull event)
{
char name[MAP_LAYER_NAME_MAX];
MAP *m = AG_PTR(1);
AG_Textbox *tb = AG_PTR(2);
AG_TextboxCopyString(tb, name, sizeof(name));
if (MAP_PushLayer(m, name) != 0) {
AG_TextMsgFromError();
} else {
AG_TextboxPrintf(tb, NULL);
}
}
#if 0
static void
EditItemProps(AG_Event *_Nonnull event)
{
MAP_View *mv = AG_SELF();
int button = AG_INT(1);
int x = AG_INT(2);
int y = AG_INT(3);
int xoffs = AG_INT(4);
int yoffs = AG_INT(5);
MAP_Item *r;
AG_Window *pwin, *win;
AG_MSpinbutton *msb;
if ((r = MAP_ItemLocate(mv->map, mv->mouse.xmap, mv->mouse.ymap,
mv->cam)) == NULL) {
return;
}
win = AG_WindowNew(0);
AG_WindowSetCaption(win, _("Map item"));
AG_WindowSetPosition(win, AG_WINDOW_MIDDLE_LEFT, 1);
AG_LabelNew(win, 0, _("Type: %s"),
(r->type == MAP_ITEM_TILE) ? _("Tile") :
(r->type == MAP_ITEM_ANIM) ? _("Animation") :
(r->type == MAP_ITEM_WARP) ? _("Warp point") : "?");
msb = AG_MSpinbuttonNew(win, 0, ",", _("Centering: "));
AG_BindSint16(msb, "xvalue", &r->r_gfx.xcenter);
AG_BindSint16(msb, "yvalue", &r->r_gfx.ycenter);
msb = AG_MSpinbuttonNew(win, 0, ",", _("Motion: "));
AG_BindSint16(msb, "xvalue", &r->r_gfx.xmotion);
AG_BindSint16(msb, "yvalue", &r->r_gfx.ymotion);
msb = AG_MSpinbuttonNew(win, 0, ",", _("Origin: "));
AG_BindSint16(msb, "xvalue", &r->r_gfx.xorigin);
AG_BindSint16(msb, "yvalue", &r->r_gfx.yorigin);
AG_SeparatorNew(win, AG_SEPARATOR_HORIZ);
msb = AG_MSpinbuttonNew(win, 0, ",", _("Source coords: "));
AG_BindSint16(msb, "xvalue", &r->r_gfx.rs.x);
AG_BindSint16(msb, "yvalue", &r->r_gfx.rs.y);
msb = AG_MSpinbuttonNew(win, 0, "x", _("Source dims: "));
AG_BindSint16(msb, "xvalue", &r->r_gfx.rs.w);
AG_BindSint16(msb, "yvalue", &r->r_gfx.rs.h);
AG_SeparatorNew(win, AG_SEPARATOR_HORIZ);
AG_NumericalNewUint8(win, 0, NULL, _("Layer: "), &r->layer);
AG_NumericalNewUint8(win, 0, NULL, _("Friction: "), &r->friction);
if ((pwin = AG_WidgetParentWindow(mv)) != NULL) {
AG_WindowAttach(pwin, win);
}
AG_WindowShow(win);
}
#endif
/* Set the attribute edition mode. */
static void
EditPropMode(AG_Event *_Nonnull event)
{
MAP_View *mv = AG_PTR(1);
int flag = AG_INT(2);
if (flag != 0) {
mv->mode = MAP_VIEW_EDIT_ATTRS;
mv->edit_attr = flag;
} else {
mv->mode = MAP_VIEW_EDIT;
}
}
static void
Undo(AG_Event *_Nonnull event)
{
MAP_Undo(AG_PTR(1));
}
static void
Redo(AG_Event *_Nonnull event)
{
MAP_Redo(AG_PTR(1));
}
static void
CenterViewToOrigin(AG_Event *_Nonnull event)
{
MAP_View *mv = AG_PTR(1);
AGMCAM(mv).x = mv->map->origin.x*AGMTILESZ(mv) - AGMTILESZ(mv)/2;
AGMCAM(mv).y = mv->map->origin.y*AGMTILESZ(mv) - AGMTILESZ(mv)/2;
MAP_ViewUpdateCamera(mv);
}
/* Detach an actor object. */
static void
DetachActor(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_PTR(1);
MAP *m = AG_PTR(2);
AG_TlistItem *it;
if ((it = AG_TlistSelectedItem(tl)) != NULL &&
strcmp(it->cat, "actor") == 0) {
MAP_Actor *a = it->p1;
TAILQ_REMOVE(&m->actors, a, actors);
MAP_DetachActor(m, a);
}
}
/* Control an actor object. */
static void
SelectActor(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_PTR(1);
MAP_View *mv = AG_PTR(2);
AG_TlistItem *it;
if ((it = AG_TlistSelectedItem(tl)) != NULL &&
strcmp(it->cat, "actor") == 0) {
MAP_Actor *a = it->p1;
MAP_ViewControl(mv, _("Player 1"), a);
}
}
/* Remove all items referencing any element of the given tileset. */
static void
RemoveAllRefsToTileset(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_PTR(1);
MAP_View *mv = AG_PTR(2);
AG_TlistItem *it = AG_TlistSelectedItem(tl);
RG_Tileset *ts = it->p1;
MAP *m = mv->map;
Uint x, y;
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
MAP_Node *n = &m->map[y][x];
MAP_Item *r, *r2;
for (r = TAILQ_FIRST(&n->nrefs);
r != TAILQ_END(&n->nrefs);
r = r2) {
r2 = TAILQ_NEXT(r, nrefs);
if ((r->type == MAP_ITEM_TILE &&
r->r_tile.obj == ts) ||
(r->type == MAP_ITEM_ANIM &&
r->r_anim.obj == ts))
MAP_NodeDelItem(m, n, r);
}
}
}
}
/* Remove all items referencing a given tile. */
static void
RemoveAllRefsToTile(AG_Event *_Nonnull event)
{
AG_Tlist *tl = AG_PTR(1);
MAP_View *mv = AG_PTR(2);
AG_TlistItem *it = AG_TlistSelectedItem(tl);
RG_Tile *tile = it->p1;
MAP *m = mv->map;
Uint x, y;
for (y = 0; y < m->maph; y++) {
for (x = 0; x < m->mapw; x++) {
MAP_Node *n = &m->map[y][x];
MAP_Item *r, *r2;
RG_Tile *ntile;
for (r = TAILQ_FIRST(&n->nrefs);
r != TAILQ_END(&n->nrefs);
r = r2) {
r2 = TAILQ_NEXT(r, nrefs);
if (RG_LookupTile(r->r_tile.obj, r->r_tile.id,
&ntile) == 0 && (ntile == tile))
MAP_NodeDelItem(m, n, r);
}
}
}
}
/* Generate the menu that pops up when clicking on a layer. */
static void
CreateLayerMenu(AG_Event *_Nonnull event)
{
AG_MenuItem *mi = AG_SENDER();
MAP *m = AG_PTR(1);
AG_Tlist *tlLayers = AG_PTR(2);
MAP_Layer *layer;
if ((layer = AG_TlistSelectedItemPtr(tlLayers)) == NULL) {
return;
}
AG_MenuAction(mi,
layer->visible ? _("Hide layer") : _("Show layer"), NULL,
SetLayerVisibility, "%p,%i", layer, !layer->visible);
AG_MenuAction(mi, _("Delete layer"), agIconTrash.s,
DeleteLayer, "%p,%p", m, layer);
AG_MenuAction(mi, _("Clear layer"), agIconTrash.s,
ClearLayer, "%p,%p", m, layer);
AG_MenuSeparator(mi);
AG_MenuActionKb(mi, _("Move layer up"), agIconUp.s,
AG_KEY_U, AG_KEYMOD_SHIFT,
MoveLayer, "%p,%p,%i", m, layer, 0, tlLayers);
AG_MenuActionKb(mi, _("Move layer down"), agIconDown.s,
AG_KEY_D, AG_KEYMOD_SHIFT,
MoveLayer, "%p,%p,%i", m, layer, 1, tlLayers);
}
static void *_Nullable
Edit(void *_Nonnull p)
{
MAP *m = p;
AG_Window *win;
AG_Toolbar *toolbar;
AG_Statusbar *statbar;
AG_Scrollbar *hbar, *vbar;
MAP_View *mv;
AG_Menu *menu;
AG_MenuItem *pitem;
AG_Box *hBox, *vBox;
AG_Pane *hPane, *vPane;
Uint flags = MAP_VIEW_GRID;
if ((OBJECT(m)->flags & AG_OBJECT_READONLY) == 0)
flags |= MAP_VIEW_EDIT;
if ((win = AG_WindowNew(0)) == NULL) {
return (NULL);
}
AG_WindowSetCaptionS(win, OBJECT(m)->name);
toolbar = AG_ToolbarNew(NULL, AG_TOOLBAR_VERT, 2, 0);
statbar = AG_StatusbarNew(NULL, 0);
mv = MAP_ViewNew(NULL, m, flags, toolbar, statbar);
MAP_ViewSizeHint(mv, 2, 2);
#if 0
AG_SetEvent(mv, "mapview-dblclick", EditItemProps, NULL);
#endif
menu = AG_MenuNew(win, AG_MENU_HFILL);
pitem = AG_MenuNode(menu->root, _("File"), NULL);
{
AG_MenuActionKb(pitem, _("Close map"), agIconClose.s,
AG_KEY_W, AG_KEYMOD_CTRL,
AG_WindowCloseGenEv, "%p", win);
}
pitem = AG_MenuNode(menu->root, _("Edit"), NULL);
{
AG_MenuAction(pitem, _("Undo"), NULL, Undo, "%p", m);
AG_MenuAction(pitem, _("Redo"), NULL, Redo, "%p", m);
AG_MenuSeparator(pitem);
AG_MenuAction(pitem, _("Map parameters..."), mapIconSettings.s,
EditMapParameters, "%p,%p", mv, win);
}
pitem = AG_MenuNode(menu->root, _("Attributes"), NULL);
{
AG_MenuAction(pitem, _("None"), NULL,
EditPropMode, "%p,%i", mv, 0);
AG_MenuAction(pitem, _("Walkability"), mapIconWalkable.s,
EditPropMode, "%p,%i", mv, MAP_ITEM_BLOCK);
AG_MenuAction(pitem, _("Climbability"), mapIconClimbable.s,
EditPropMode, "%p,%i", mv, MAP_ITEM_CLIMBABLE);
AG_MenuAction(pitem, _("Jumpability"), mapIconJumpable.s,
EditPropMode, "%p,%i", mv, MAP_ITEM_JUMPABLE);
AG_MenuAction(pitem, _("Slippery"), mapIconSlippery.s,
EditPropMode, "%p,%i", mv, MAP_ITEM_SLIPPERY);
}
pitem = AG_MenuNode(menu->root, _("View"), NULL);
{
extern int mapViewAnimatedBg;
AG_MenuAction(pitem, _("Create view..."), mapIconNewView.s,
CreateView, "%p, %p", mv, win);
AG_MenuAction(pitem, _("Center around origin"), mapIconOrigin.s,
CenterViewToOrigin, "%p", mv);
AG_MenuSeparator(pitem);
AG_MenuIntFlags(pitem, _("Show grid"), mapIconGrid.s,
&mv->flags, MAP_VIEW_GRID, 0);
AG_MenuIntFlags(pitem, _("Show background"), mapIconGrid.s,
&mv->flags, MAP_VIEW_NO_BG, 1);
AG_MenuIntBool(pitem, _("Animate background"), mapIconGrid.s,
&mapViewAnimatedBg, 0);
AG_MenuIntFlags(pitem, _("Show map origin"), mapIconOrigin.s,
&mv->flags, MAP_VIEW_SHOW_ORIGIN, 0);
AG_MenuIntFlags(pitem, _("Show element offsets"), mapIconGrid.s,
&mv->flags, MAP_VIEW_SHOW_OFFSETS, 0);
}
hPane = AG_PaneNewHoriz(win, AG_PANE_EXPAND);
AG_PaneSetDivisionPacking(hPane, 1, AG_BOX_HORIZ);
{
AG_Notebook *nb;
AG_NotebookTab *ntab;
AG_Tlist *tl;
AG_MenuItem *mi;
vPane = AG_PaneNewVert(hPane->div[0], AG_PANE_EXPAND);
nb = AG_NotebookNew(vPane->div[0], AG_NOTEBOOK_EXPAND);
ntab = AG_NotebookAdd(nb, _("Library"), AG_BOX_VERT);
{
tl = AG_TlistNew(ntab, AG_TLIST_POLL|AG_TLIST_TREE|
AG_TLIST_EXPAND);
#if 0
AG_SetEvent(tl, "tlist-poll", PollLibs, "%p", agWorld);
#endif
AG_SetEvent(tl, "tlist-changed", SelectLib, "%p", mv);
mv->lib_tl = tl;
WIDGET(tl)->flags &= ~(AG_WIDGET_FOCUSABLE);
mi = AG_TlistSetPopup(mv->lib_tl, "tileset");
{
AG_MenuAction(mi, _("Remove all references to"),
agIconTrash.s,
RemoveAllRefsToTileset, "%p,%p",
mv->lib_tl, mv);
}
mi = AG_TlistSetPopup(mv->lib_tl, "tile");
{
AG_MenuAction(mi, _("Remove all references to"),
agIconTrash.s,
RemoveAllRefsToTile, "%p,%p",
mv->lib_tl, mv);
}
}
ntab = AG_NotebookAdd(nb, _("Objects"), AG_BOX_VERT);
{
tl = AG_TlistNew(ntab, AG_TLIST_POLL|AG_TLIST_TREE|
AG_TLIST_EXPAND);
AG_SetEvent(tl, "tlist-poll", PollActors, "%p", mv);
// AG_SetEvent(tl, "tlist-changed", select_obj, "%p", mv);
mv->objs_tl = tl;
WIDGET(tl)->flags &= ~(AG_WIDGET_FOCUSABLE);
mi = AG_TlistSetPopup(mv->objs_tl, "actor");
{
AG_MenuAction(mi, _("Control actor"),
mapIconActor.s,
SelectActor, "%p,%p", mv->objs_tl, mv);
AG_MenuAction(mi, _("Detach actor"),
agIconTrash.s,
DetachActor, "%p,%p", mv->objs_tl, m);
}
}
ntab = AG_NotebookAdd(nb, _("Layers"), AG_BOX_VERT);
{
AG_Textbox *tb;
mv->layers_tl = AG_TlistNew(ntab, AG_TLIST_POLL|
AG_TLIST_EXPAND);
AG_TlistSetItemHeight(mv->layers_tl, MAPTILESZ);
AG_SetEvent(mv->layers_tl, "tlist-poll",
PollLayers, "%p", m);
AG_SetEvent(mv->layers_tl, "tlist-dblclick",
SelectLayer, "%p", m);
mi = AG_TlistSetPopup(mv->layers_tl, "layer");
AG_MenuSetPollFn(mi, CreateLayerMenu, "%p,%p",
m, mv->layers_tl);
hBox = AG_BoxNew(ntab, AG_BOX_HORIZ, AG_BOX_HFILL);
tb = AG_TextboxNewS(hBox, 0, _("Name: "));
AG_SetEvent(tb, "textbox-return",
PushLayer, "%p, %p", m, tb);
AG_ButtonNewFn(ntab, AG_BUTTON_HFILL, _("Push"),
PushLayer, "%p, %p", m, tb);
}
AG_SeparatorNew(hPane->div[0], AG_SEPARATOR_HORIZ);
vbar = AG_ScrollbarNew(hPane->div[1], AG_SCROLLBAR_VERT, 0);
vBox = AG_BoxNew(hPane->div[1], AG_BOX_VERT, AG_BOX_EXPAND);
{
AG_ObjectAttach(vBox, mv);
hbar = AG_ScrollbarNew(vBox, AG_SCROLLBAR_HORIZ, 0);
}
AG_ObjectAttach(hPane->div[1], toolbar);
}
pitem = AG_MenuNode(menu->root, _("Tools"), NULL);
{
const MAP_ToolOps *ops[] = {
&mapNodeselOps,
&mapRefselOps,
&mapFillOps,
&mapEraserOps,
&mapFlipOps,
&mapInvertOps,
&mapInsertOps,
&mapGInsertOps
};
const int nops = sizeof(ops) / sizeof(ops[0]);
MAP_Tool *t;
int i;
for (i = 0; i < nops; i++) {
t = MAP_ViewRegTool(mv, ops[i], m);
t->pane = (void *)vPane->div[1];
AG_MenuAction(pitem, _(ops[i]->desc),
(ops[i]->icon!=NULL) ? ops[i]->icon->s : NULL,
SelectTool, "%p, %p", mv, t);
}
}
MAP_ViewUseScrollbars(mv, hbar, vbar);
AG_ObjectAttach(win, statbar);
AG_PaneMoveDividerPct(hPane, 40);
AG_WindowSetGeometryAlignedPct(win, AG_WINDOW_MC, 60, 40);
AG_WidgetReplaceSurface(mv->status, mv->status->surface,
AG_TextRender(
_("Select a tool or double-click on an element to insert.")));
AG_WidgetFocus(mv);
return (win);
}
AG_ObjectClass mapClass = {
"MAP",
sizeof(MAP),
{ 11, 0 },
Init,
Reset,
Destroy,
Load,
Save,
Edit
};