src/newgrf_fsmports.cpp
author richk
Fri, 25 Apr 2008 02:15:34 +0000
branchNewGRF_ports
changeset 10349 ff900a23e102
parent 10348 a9b20fd61175
permissions -rw-r--r--
(svn r12890) [NewGRF_ports] -Change: Add comment to new function, and correct comment in old.
/* $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_base.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 "cargotype.h"
#include "town_map.h"
#include "newgrf_town.h"
#include "date_func.h"
#include "player_func.h"
#include "gfx_func.h"
#include "strings_func.h"
#include "widgets/dropdown_type.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)
{
	if (cls == SYSTEM_HIDDEN_CLASS) {
		fsmports_classes[31].id = cls;
		return FSMPORTS_CLASS_SYST;
	}
	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
 */
DropDownList *BuildFSMportsClassDropdown()
{
	DropDownList *list = new DropDownList();

	for (uint i = 0; i < GetNumFSMportsClasses() && fsmports_classes[i].id != 0; i++) {
		if (fsmports_classes[i].id == SYSTEM_HIDDEN_CLASS) continue;
		list->push_back(new DropDownListStringItem(GetFSMportsClassName((FSMportsClassID)i), i, false));
	}

	return list;
}

/**
 * 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.
 */
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;
}

TileIndexDiffC RotateFSMPosition(TileIndexDiffC position, byte size_x, byte size_y, byte FSM_orientation)
{
	TileIndexDiffC new_position;

	switch (FSM_orientation) {
		case DIR_NE:
			new_position = position;
			break;
		case DIR_SE:
			new_position.x = position.y;
			new_position.y = size_y - (position.x + 1);
			/*note that size_y is used here, because the size_x is the size_x of the set, which is size_y in the 01 orientation */
			break;
		case DIR_SW:
			new_position.x = size_x - (position.x + 1);
			new_position.y = size_y - (position.y + 1);
			break;
		case DIR_NW:
			new_position.x = size_x - (position.y + 1);
			/*note that size_x is used here, because the size_y is the size_y of the set, which is size_x in the 01 orientation */
			new_position.y = position.x;
			break;
		default:
			new_position = position;
			break;
	}

	return new_position;
}

/* 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 (HasBit(st->goods[cargo_type].acceptance_pickup, GoodsEntry::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 GB(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE, 1) << 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) | (GB(g->acceptance_pickup, GoodsEntry::ACCEPTANCE, 1) << 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_SYST) {
		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.
 * @param st Station to work with.
 * @param specindex Index of the custom station within the FSMports's spec list.
 * @return None.
 */
void DeallocateSpecFromFSMports(Station* st, byte specindex)
{
	/* specindex of 0 (default) is never freeable */
	if (specindex == 0) return;

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

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

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

/** Remove a FSMportsSpec from a Station, by reference of the spec itself.
 * @param st Station to work with.
 * @param fsmportspec of the custom spec.
 * @return None.
 */
void RemoveFSMSpecFromStationList(Station *st, const FSMportsSpec *fsmportspec)
{
	uint i;

	if (fsmportspec == NULL) return;

	/* 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) break;
	}

	if (i < MAX_SPECLIST) {
		/* This specindex is no longer in use, so deallocate it */
		DeallocateSpecFromFSMports(st, i);
	}
}

/** Draw representation of a station tile for GUI purposes.
 * @param x Position x of image.
 * @param y Position y of image.
 * @param tile tile ID from action 1 set
 * @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, byte tile, FSMportsClassID sclass, uint FSMports)
{
	const FSMportsSpec *fsmportsspec;
	const DrawTileSprites *sprites;
	const DrawTileSeqStruct *seq;
	SpriteID relocation;
	SpriteID image;
	SpriteID pal = PLAYER_SPRITE_COLOR(_local_player);

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

	relocation = GetCustomFSMportsRelocation(fsmportsspec, NULL, INVALID_TILE);

	assert(fsmportsspec->renderdata != NULL);

	sprites = &fsmportsspec->renderdata[tile];

	image = sprites->ground.sprite;
	image += relocation;
	DrawSprite(image, PAL_NONE, x, y);

	foreach_draw_tile_seq(seq, sprites->seq) {
		Point pt;
		image = seq->image.sprite;
		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;
}