src/newgrf_fsmports.cpp
author richk
Fri, 03 Aug 2007 18:10:15 +0000
branchNewGRF_ports
changeset 6743 cabfaa4a0295
parent 6739 3f2ca4d0abda
child 6745 f45a41940079
permissions -rw-r--r--
(svn r10766) [NewGRF_ports] -Sync: with trunk r10651-10765
/* $Id$ */

/** @file newgrf_station.cpp Functions for dealing with station classes and custom stations. */

#include "stdafx.h"
#include "openttd.h"
#include "variables.h"
#include "functions.h"
#include "landscape.h"
#include "debug.h"
#include "sprite.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "station.h"
#include "station_map.h"
#include "newgrf.h"
#include "newgrf_callbacks.h"
#include "newgrf_commons.h"
#include "newgrf_station.h"
#include "newgrf_spritegroup.h"
#include "date.h"
#include "helpers.hpp"
#include "cargotype.h"
#include "town_map.h"
#include "newgrf_town.h"

static FSMportsClass fsmports_classes[FSMPORTS_CLASS_MAX];

enum {
	MAX_SPECLIST = 255,
};

/**
 * Reset station classes to their default state.
 * This includes initialising the Default and Waypoint classes with an empty
 * entry, for standard stations and waypoints.
 */
void ResetFSMportsClasses()
{
	for (FSMportsClassID i = FSMPORTS_CLASS_BEGIN; i < FSMPORTS_CLASS_MAX; i++) {
		fsmports_classes[i].id = 0;
		fsmports_classes[i].name = STR_EMPTY;
		fsmports_classes[i].FSMports = 0;

		free(fsmports_classes[i].spec);
		fsmports_classes[i].spec = NULL;
	}

	/* Set up initial data */
	//fsmports_classes[0].id = 'DFLT';
	//fsmports_classes[0].name = STR_STAT_CLASS_DFLT;
	//fsmports_classes[0].FSMports = 1;
	//fsmports_classes[0].spec = MallocT<FSMportsSpec*>(1);
	//fsmports_classes[0].spec[0] = NULL;

	//fsmports_classes[1].id = 'WAYP';
	//fsmports_classes[1].name = STR_STAT_CLASS_WAYP;
	//fsmports_classes[1].FSMports = 1;
	//fsmports_classes[1].spec = MallocT<FSMportsSpec*>(1);
	//fsmports_classes[1].spec[0] = NULL;
}

/**
 * Allocate a station class for the given class id.
 * @param cls A 32 bit value identifying the class.
 * @return Index into station_classes of allocated class.
 */
FSMportsClassID AllocateFSMportsClass(uint32 cls)
{
	for (FSMportsClassID i = FSMPORTS_CLASS_BEGIN; i < FSMPORTS_CLASS_MAX; i++) {
		if (fsmports_classes[i].id == cls) {
			/* ClassID is already allocated, so reuse it. */
			return i;
		} else if (fsmports_classes[i].id == 0) {
			/* This class is empty, so allocate it to the ClassID. */
			fsmports_classes[i].id = cls;
			return i;
		}
	}

	grfmsg(2, "FSMportsClassAllocate: already allocated %d classes, using default", FSMPORTS_CLASS_MAX);
	return FSMPORTS_CLASS_DFLT;
}

/** Set the name of a custom station class */
void SetFSMportsClassName(FSMportsClassID sclass, StringID name)
{
	assert(sclass < FSMPORTS_CLASS_MAX);
	fsmports_classes[sclass].name = name;
}

/** Retrieve the name of a custom station class */
StringID GetFSMportsClassName(FSMportsClassID sclass)
{
	assert(sclass < FSMPORTS_CLASS_MAX);
	return fsmports_classes[sclass].name;
}

/** Build a list of station class name StringIDs to use in a dropdown list
 * @return Pointer to a (static) array of StringIDs
 */
StringID *BuildFSMportsClassDropdown()
{
	/* Allow room for all station classes, plus a terminator entry */
	static StringID names[FSMPORTS_CLASS_MAX + 1];
	uint i;

	/* Add each name */
	for (i = 0; i < FSMPORTS_CLASS_MAX && fsmports_classes[i].id != 0; i++) {
		names[i] = fsmports_classes[i].name;
	}
	/* Terminate the list */
	names[i] = INVALID_STRING_ID;

	return names;
}

/**
 * Get the number of station classes in use.
 * @return Number of station classes.
 */
uint GetNumFSMportsClasses()
{
	uint i;
	for (i = 0; i < FSMPORTS_CLASS_MAX && fsmports_classes[i].id != 0; i++);
	return i;
}

/**
 * Return the number of stations for the given station class.
 * @param sclass Index of the station class.
 * @return Number of stations in the class.
 */
uint GetNumCustomFSMports(FSMportsClassID sclass)
{
	assert(sclass < FSMPORTS_CLASS_MAX);
	return fsmports_classes[sclass].FSMports;
}

/**
 * Tie a fsmport spec to its class.
 * @param fsmportspec The station spec.
 */
void SetCustomFSMportsSpec(FSMportsSpec *fsmportspec)
{
	FSMportsClass *fsmports_class;
	int i;

	/* If the station has already been allocated, don't reallocate it. */
	if (fsmportspec->allocated) return;

	assert(fsmportspec->sclass < FSMPORTS_CLASS_MAX);
	fsmports_class = &fsmports_classes[fsmportspec->sclass];

	i = fsmports_class->FSMports++;
	fsmports_class->spec = ReallocT(fsmports_class->spec, fsmports_class->FSMports);

	fsmports_class->spec[i] = fsmportspec;
	fsmportspec->allocated = true;
}

/**
 * Retrieve a fsmspec from a class.
 * @param sclass Index of the fsm class.
 * @param fsport The index with the class.
 * @return The fsmspec.
 */
const FSMportsSpec *GetCustomFSMportsSpec(FSMportsClassID sclass, uint fsmport)
{
	assert(sclass < FSMPORTS_CLASS_MAX);
	if (fsmport < fsmports_classes[sclass].FSMports)
		return fsmports_classes[sclass].spec[fsmport];

	/* If the custom fsmport isn't defined any more, then the GRF file
	 * probably was not loaded. */
	return NULL;
}


const FSMportsSpec *GetCustomFSMportsSpecByGrf(uint32 grfid, byte localidx)
{
	uint j;

	for (FSMportsClassID i = FSMPORTS_CLASS_BEGIN; i < FSMPORTS_CLASS_MAX; i++) {
		for (j = 0; j < fsmports_classes[i].FSMports; j++) {
			const FSMportsSpec *fsmportspec = fsmports_classes[i].spec[j];
			if (fsmportspec == NULL) continue;
			if (fsmportspec->grffile->grfid == grfid && fsmportspec->localidx == localidx) return fsmportspec;
		}
	}

	return NULL;
}


/* FSMports Resolver Functions */
static uint32 FSMportsGetRandomBits(const ResolverObject *object)
{
	const Station *st = object->u.station.st;
	const TileIndex tile = object->u.station.tile;
	return (st == NULL ? 0 : st->random_bits) | (tile == INVALID_TILE ? 0 : GetStationTileRandomBits(tile) << 16);
}


static uint32 FSMportsGetTriggers(const ResolverObject *object)
{
	const Station *st = object->u.station.st;
	return st == NULL ? 0 : st->waiting_triggers;
}


static void FSMportsSetTriggers(const ResolverObject *object, int triggers)
{
	Station *st = (Station*)object->u.station.st;
	assert(st != NULL);
	st->waiting_triggers = triggers;
}

/**
 * FSMports variable cache
 * This caches 'expensive' station variable lookups which iterate over
 * several tiles that may be called multiple times per Resolve().
 */
static struct {
	uint32 v40;
	uint32 v41;
	uint32 v45;
	uint32 v46;
	uint32 v47;
	uint32 v49;
	uint8 valid;
} _svc;

static uint32 FSMportsGetVariable(const ResolverObject *object, byte variable, byte parameter, bool *available)
{
	const Station *st = object->u.station.st;
	TileIndex tile = object->u.station.tile;

	if (object->scope == VSG_SCOPE_PARENT) {
		/* Pass the request on to the town of the station */
		Town *t;

		if (st != NULL) {
			t = st->town;
		} else if (tile != INVALID_TILE) {
			t = GetTownByTile(tile);
		} else {
			*available = false;
			return UINT_MAX;
		}

		return TownGetVariable(variable, parameter, available, t);
	}

	if (st == NULL) {
		/* Station does not exist, so we're in a purchase list */
		switch (variable) {
			case 0x40:
			case 0x41:
			case 0x46:
			case 0x47:
			case 0x49: return 0x2110000;       // Platforms, tracks & position
			case 0x42: return 0;               // Rail type (XXX Get current type from GUI?)
			case 0x43: return _current_player; // Station owner
			case 0x44: return 2;               // PBS status
			case 0xFA: return clamp(_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0, 65535); // Build date, clamped to a 16 bit value
		}

		*available = false;
		return UINT_MAX;
	}

	switch (variable) {
		/* Calculated station variables */
		case 0x42: return GetTerrainType(tile) | (GetRailType(tile) << 8);
		case 0x43: return st->owner; // Station owner
		case 0x48: { // Accepted cargo types
			CargoID cargo_type;
			uint32 value = 0;

			for (cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) {
				if (st->goods[cargo_type].acceptance) SETBIT(value, cargo_type);
			}
			return value;
		}
		/* Variables which use the parameter */
		/* Variables 0x60 to 0x65 are handled separately below */

		/* General station properties */
		case 0x82: return 50;
		case 0x84: return st->string_id;
		case 0x86: return 0;
		case 0x8A: return st->had_vehicle_of_type;
		case 0xF0: return st->facilities;
		case 0xF1: return st->airport_type;
		case 0xF2: return st->truck_stops->status;
		case 0xF3: return st->bus_stops->status;
		//TODO: need to decide how/what to return
		//case 0xF6: return st->airport_flags;
		//case 0xF7: return GB(st->airport_flags, 8, 8);
		case 0xFA: return clamp(st->build_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0, 65535);
	}

	/* Handle cargo variables with parameter, 0x60 to 0x65 */
	if (variable >= 0x60 && variable <= 0x65) {
		CargoID c = GetCargoTranslation(parameter, object->u.station.statspec->grffile);

		if (c == CT_INVALID) return 0;
		const GoodsEntry *ge = &st->goods[c];

		switch (variable) {
			case 0x60: return min(ge->cargo.Count(), 4095);
			case 0x61: return ge->days_since_pickup;
			case 0x62: return ge->rating;
			case 0x63: return ge->cargo.DaysInTransit();
			case 0x64: return ge->last_speed | (ge->last_age << 8);
			case 0x65: return ge->acceptance << 3;
		}
	}

	/* Handle cargo variables (deprecated) */
	if (variable >= 0x8C && variable <= 0xEC) {
		const GoodsEntry *g = &st->goods[GB(variable - 0x8C, 3, 4)];
		switch (GB(variable - 0x8C, 0, 3)) {
			case 0: return g->cargo.Count();
			case 1: return GB(min(g->cargo.Count(), 4095), 0, 4) | (g->acceptance << 7);
			case 2: return g->days_since_pickup;
			case 3: return g->rating;
			case 4: return g->cargo.Source();
			case 5: return g->cargo.DaysInTransit();
			case 6: return g->last_speed;
			case 7: return g->last_age;
		}
	}

	DEBUG(grf, 1, "Unhandled station property 0x%X", variable);

	*available = false;
	return UINT_MAX;
}


static const SpriteGroup *FSMportsResolveReal(const ResolverObject *object, const SpriteGroup *group)
{
	const Station *st = object->u.station.st;
	const FSMportsSpec *fsmportspec = object->u.station.fsmportspec;
	uint set;

	uint cargo = 0;
	CargoID cargo_type = object->u.station.cargo_type;

	if (st == NULL || fsmportspec->sclass == FSMPORTS_CLASS_WAYP) {
		return group->g.real.loading[0];
	}

	switch (cargo_type) {
		case CT_INVALID:
		case CT_DEFAULT_NA:
		case CT_PURCHASE:
			cargo = 0;
			break;

		case CT_DEFAULT:
			for (cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) {
				cargo += st->goods[cargo_type].cargo.Count();
			}
			break;

		default:
			cargo = st->goods[cargo_type].cargo.Count();
			break;
	}

	if (HASBIT(fsmportspec->flags, 1)) cargo /= (st->trainst_w + st->trainst_h);
	cargo = min(0xfff, cargo);

	if (cargo > fsmportspec->cargo_threshold) {
		if (group->g.real.num_loading > 0) {
			set = ((cargo - fsmportspec->cargo_threshold) * group->g.real.num_loading) / (4096 - fsmportspec->cargo_threshold);
			return group->g.real.loading[set];
		}
	} else {
		if (group->g.real.num_loaded > 0) {
			set = (cargo * group->g.real.num_loaded) / (fsmportspec->cargo_threshold + 1);
			return group->g.real.loaded[set];
		}
	}

	return group->g.real.loading[0];
}


static void NewFSMportsResolver(ResolverObject *res, const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
{
	res->GetRandomBits = FSMportsGetRandomBits;
	res->GetTriggers   = FSMportsGetTriggers;
	res->SetTriggers   = FSMportsSetTriggers;
	res->GetVariable   = FSMportsGetVariable;
	res->ResolveReal   = FSMportsResolveReal;

	res->u.station.st       = st;
	res->u.station.fsmportspec = fsmportspec;
	res->u.station.tile     = tile;

	res->callback        = CBID_NO_CALLBACK;
	res->callback_param1 = 0;
	res->callback_param2 = 0;
	res->last_value      = 0;
	res->trigger         = 0;
	res->reseed          = 0;
}

static const SpriteGroup *ResolveFSMports(ResolverObject *object)
{
	const SpriteGroup *group;
	CargoID ctype = CT_DEFAULT_NA;

	if (object->u.station.st == NULL) {
		/* No station, so we are in a purchase list */
		ctype = CT_PURCHASE;
	} else {
		/* Pick the first cargo that we have waiting */
		for (CargoID cargo = 0; cargo < NUM_CARGO; cargo++) {
			const CargoSpec *cs = GetCargo(cargo);
			if (cs->IsValid() && object->u.station.fsmportspec->spritegroup[cargo] != NULL &&
					!object->u.station.st->goods[cargo].cargo.Empty()) {
				ctype = cargo;
				break;
			}
		}
	}

	group = object->u.station.fsmportspec->spritegroup[ctype];
	if (group == NULL) {
		ctype = CT_DEFAULT;
		group = object->u.station.fsmportspec->spritegroup[ctype];
	}

	if (group == NULL) return NULL;

	/* Remember the cargo type we've picked */
	object->u.station.cargo_type = ctype;

	/* Invalidate all cached vars */
	_svc.valid = 0;

	return Resolve(group, object);
}

SpriteID GetCustomFSMportsRelocation(const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
{
	const SpriteGroup *group;
	ResolverObject object;

	NewFSMportsResolver(&object, fsmportspec, st, tile);

	group = ResolveFSMports(&object);
	if (group == NULL || group->type != SGT_RESULT) return 0;
	return group->g.result.sprite - 0x42D;
}


SpriteID GetCustomFSMportsGroundRelocation(const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
{
	const SpriteGroup *group;
	ResolverObject object;

	NewFSMportsResolver(&object, fsmportspec, st, tile);
	object.callback_param1 = 1; // Indicate we are resolving the ground sprite

	group = ResolveFSMports(&object);
	if (group == NULL || group->type != SGT_RESULT) return 0;
	return group->g.result.sprite - 0x42D;
}


uint16 GetFSMportsCallback(CallbackID callback, uint32 param1, uint32 param2, const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
{
	const SpriteGroup *group;
	ResolverObject object;

	NewFSMportsResolver(&object, fsmportspec, st, tile);

	object.callback        = callback;
	object.callback_param1 = param1;
	object.callback_param2 = param2;

	group = ResolveFSMports(&object);
	if (group == NULL || group->type != SGT_CALLBACK) return CALLBACK_FAILED;
	return group->g.callback.result;
}


/**
 * Allocate a FSMportsSpec to a Station. This is called once per build operation.
 * @param statspec FSMportsSpec to allocate.
 * @param st Station to allocate it to.
 * @param exec Whether to actually allocate the spec.
 * @return Index within the FSMports's spec list, or -1 if the allocation failed.
 */
int AllocateFSMportsSpecToStation(const FSMportsSpec *fsmportspec, Station *st, bool exec)
{
	uint i;

	if (fsmportspec == NULL) return 0;

	/* Check if this spec has already been allocated */
	for (i = 1; i < st->num_fsmportsspecs && i < MAX_SPECLIST; i++) {
		if (st->fsmportsspeclist[i].spec == fsmportspec) return i;
	}

	for (i = 1; i < st->num_fsmportsspecs && i < MAX_SPECLIST; i++) {
		if (st->fsmportsspeclist[i].spec == NULL && st->fsmportsspeclist[i].grfid == 0) break;
	}

	if (i == MAX_SPECLIST) return -1;

	if (exec) {
		if (i >= st->num_fsmportsspecs) {
			st->num_fsmportsspecs = i + 1;
			st->fsmportsspeclist = ReallocT(st->fsmportsspeclist, st->num_fsmportsspecs);

			if (st->num_fsmportsspecs == 2) {
				/* Initial allocation */
				st->fsmportsspeclist[0].spec     = NULL;
				st->fsmportsspeclist[0].grfid    = 0;
				st->fsmportsspeclist[0].localidx = 0;
			}
		}

		st->fsmportsspeclist[i].spec     = fsmportspec;
		st->fsmportsspeclist[i].grfid    = fsmportspec->grffile->grfid;
		st->fsmportsspeclist[i].localidx = fsmportspec->localidx;
	}

	return i;
}


/** Deallocate a FSMportsSpec from a Station. Called when removing a single station tile.
 * @param st Station to work with.
 * @param specindex Index of the custom station within the FSMports's spec list.
 * @return Indicates whether the FSMportsSpec was deallocated.
 */
void DeallocateSpecFromFSMports(Station* st, byte specindex)
{
	/* specindex of 0 (default) is never freeable */
	if (specindex == 0) return;

	/* Check all tiles over the station to check if the specindex is still in use */
	BEGIN_TILE_LOOP(tile, st->trainst_w, st->trainst_h, st->train_tile) {
		if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st->index && GetCustomStationSpecIndex(tile) == specindex) {
			return;
		}
	} END_TILE_LOOP(tile, st->trainst_w, st->trainst_h, st->train_tile)

	/* This specindex is no longer in use, so deallocate it */
	st->speclist[specindex].spec     = NULL;
	st->speclist[specindex].grfid    = 0;
	st->speclist[specindex].localidx = 0;

	/* If this was the highest spec index, reallocate */
	if (specindex == st->num_specs - 1) {
		for (; st->speclist[st->num_specs - 1].grfid == 0 && st->num_specs > 1; st->num_specs--);

		if (st->num_specs > 1) {
			st->speclist = ReallocT(st->speclist, st->num_specs);
		} else {
			free(st->speclist);
			st->num_specs = 0;
			st->speclist  = NULL;
		}
	}
}

/** Draw representation of a station tile for GUI purposes.
 * @param x Position x of image.
 * @param y Position y of image.
 * @param axis Axis.
 * @param railtype Rail type.
 * @param sclass, station Type of station.
 * @param station station ID
 * @return True if the tile was drawn (allows for fallback to default graphic)
 */
bool DrawFSMportsTile(int x, int y, RailType railtype, Axis axis, FSMportsClassID sclass, uint station)
{
	const FSMportsSpec *fsmportsspec;
	const DrawTileSprites *sprites;
	const DrawTileSeqStruct *seq;
	const RailtypeInfo *rti = GetRailTypeInfo(railtype);
	SpriteID relocation;
	SpriteID image;
	SpriteID pal = PLAYER_SPRITE_COLOR(_local_player);
	uint tile = 2;

	fsmportsspec = GetCustomFSMportsSpec(sclass, station);
	if (fsmportsspec == NULL) return false;

	relocation = GetCustomFSMportsRelocation(fsmportsspec, NULL, INVALID_TILE);

	if (HASBIT(fsmportsspec->callbackmask, CBM_CUSTOM_LAYOUT)) {
		uint16 callback = GetFSMportsCallback(CBID_STATION_SPRITE_LAYOUT, 0x2110000, 0, fsmportsspec, NULL, INVALID_TILE);
		if (callback != CALLBACK_FAILED) tile = callback;
	}

	if (fsmportsspec->renderdata == NULL) {
		sprites = GetStationTileLayout(STATION_AIRPORT, tile + axis);
	} else {
		sprites = &fsmportsspec->renderdata[(tile < fsmportsspec->tiles) ? tile + axis : (uint)axis];
	}

	image = sprites->ground_sprite;
	if (HASBIT(image, SPRITE_MODIFIER_USE_OFFSET)) {
		image += GetCustomFSMportsGroundRelocation(fsmportsspec, NULL, INVALID_TILE);
		image += rti->custom_ground_offset;
	} else {
		image += rti->total_offset;
	}

	DrawSprite(image, PAL_NONE, x, y);

	foreach_draw_tile_seq(seq, sprites->seq) {
		Point pt;
		image = seq->image;
		if (HASBIT(image, SPRITE_MODIFIER_USE_OFFSET)) {
			image += rti->total_offset;
		} else {
			image += relocation;
		}

		if ((byte)seq->delta_z != 0x80) {
			pt = RemapCoords(seq->delta_x, seq->delta_y, seq->delta_z);
			DrawSprite(image, pal, x + pt.x, y + pt.y);
		}
	}

	return true;
}


static const FSMportsSpec* GetFSMportsSpec(TileIndex t)
{
	const Station* st;
	uint specindex;

	if (!IsCustomStationSpecIndex(t)) return NULL;

	st = GetStationByTile(t);
	specindex = GetCustomStationSpecIndex(t);
	return specindex < st->num_fsmportsspecs ? st->fsmportsspeclist[specindex].spec : NULL;
}