src/newgrf_fsmports.cpp
branchNewGRF_ports
changeset 6722 72f280229ee1
child 6730 f09255ea0123
equal deleted inserted replaced
6721:b3b72a7f4c50 6722:72f280229ee1
       
     1 /* $Id$ */
       
     2 
       
     3 /** @file newgrf_station.cpp Functions for dealing with station classes and custom stations. */
       
     4 
       
     5 #include "stdafx.h"
       
     6 #include "openttd.h"
       
     7 #include "variables.h"
       
     8 #include "functions.h"
       
     9 #include "landscape.h"
       
    10 #include "debug.h"
       
    11 #include "sprite.h"
       
    12 #include "table/sprites.h"
       
    13 #include "table/strings.h"
       
    14 #include "station.h"
       
    15 #include "station_map.h"
       
    16 #include "newgrf.h"
       
    17 #include "newgrf_callbacks.h"
       
    18 #include "newgrf_commons.h"
       
    19 #include "newgrf_station.h"
       
    20 #include "newgrf_spritegroup.h"
       
    21 #include "date.h"
       
    22 #include "helpers.hpp"
       
    23 #include "cargotype.h"
       
    24 #include "town_map.h"
       
    25 #include "newgrf_town.h"
       
    26 
       
    27 static FSMportsClass fsmports_classes[FSMPORTS_CLASS_MAX];
       
    28 
       
    29 enum {
       
    30 	MAX_SPECLIST = 255,
       
    31 };
       
    32 
       
    33 /**
       
    34  * Reset station classes to their default state.
       
    35  * This includes initialising the Default and Waypoint classes with an empty
       
    36  * entry, for standard stations and waypoints.
       
    37  */
       
    38 void ResetFSMportsClasses()
       
    39 {
       
    40 	for (FSMportsClassID i = FSMPORTS_CLASS_BEGIN; i < FSMPORTS_CLASS_MAX; i++) {
       
    41 		fsmports_classes[i].id = 0;
       
    42 		fsmports_classes[i].name = STR_EMPTY;
       
    43 		fsmports_classes[i].FSMports = 0;
       
    44 
       
    45 		free(fsmports_classes[i].spec);
       
    46 		fsmports_classes[i].spec = NULL;
       
    47 	}
       
    48 
       
    49 	/* Set up initial data */
       
    50 	//fsmports_classes[0].id = 'DFLT';
       
    51 	//fsmports_classes[0].name = STR_STAT_CLASS_DFLT;
       
    52 	//fsmports_classes[0].FSMports = 1;
       
    53 	//fsmports_classes[0].spec = MallocT<FSMportsSpec*>(1);
       
    54 	//fsmports_classes[0].spec[0] = NULL;
       
    55 
       
    56 	//fsmports_classes[1].id = 'WAYP';
       
    57 	//fsmports_classes[1].name = STR_STAT_CLASS_WAYP;
       
    58 	//fsmports_classes[1].FSMports = 1;
       
    59 	//fsmports_classes[1].spec = MallocT<FSMportsSpec*>(1);
       
    60 	//fsmports_classes[1].spec[0] = NULL;
       
    61 }
       
    62 
       
    63 /**
       
    64  * Allocate a station class for the given class id.
       
    65  * @param cls A 32 bit value identifying the class.
       
    66  * @return Index into station_classes of allocated class.
       
    67  */
       
    68 FSMportsClassID AllocateFSMportsClass(uint32 cls)
       
    69 {
       
    70 	for (FSMportsClassID i = FSMPORTS_CLASS_BEGIN; i < FSMPORTS_CLASS_MAX; i++) {
       
    71 		if (fsmports_classes[i].id == cls) {
       
    72 			/* ClassID is already allocated, so reuse it. */
       
    73 			return i;
       
    74 		} else if (fsmports_classes[i].id == 0) {
       
    75 			/* This class is empty, so allocate it to the ClassID. */
       
    76 			fsmports_classes[i].id = cls;
       
    77 			return i;
       
    78 		}
       
    79 	}
       
    80 
       
    81 	grfmsg(2, "FSMportsClassAllocate: already allocated %d classes, using default", FSMPORTS_CLASS_MAX);
       
    82 	return FSMPORTS_CLASS_DFLT;
       
    83 }
       
    84 
       
    85 /** Set the name of a custom station class */
       
    86 void SetFSMportsClassName(FSMportsClassID sclass, StringID name)
       
    87 {
       
    88 	assert(sclass < FSMPORTS_CLASS_MAX);
       
    89 	fsmports_classes[sclass].name = name;
       
    90 }
       
    91 
       
    92 /** Retrieve the name of a custom station class */
       
    93 StringID GetFSMportsClassName(FSMportsClassID sclass)
       
    94 {
       
    95 	assert(sclass < FSMPORTS_CLASS_MAX);
       
    96 	return fsmports_classes[sclass].name;
       
    97 }
       
    98 
       
    99 /** Build a list of station class name StringIDs to use in a dropdown list
       
   100  * @return Pointer to a (static) array of StringIDs
       
   101  */
       
   102 StringID *BuildFSMportsClassDropdown()
       
   103 {
       
   104 	/* Allow room for all station classes, plus a terminator entry */
       
   105 	static StringID names[FSMPORTS_CLASS_MAX + 1];
       
   106 	uint i;
       
   107 
       
   108 	/* Add each name */
       
   109 	for (i = 0; i < FSMPORTS_CLASS_MAX && fsmports_classes[i].id != 0; i++) {
       
   110 		names[i] = fsmports_classes[i].name;
       
   111 	}
       
   112 	/* Terminate the list */
       
   113 	names[i] = INVALID_STRING_ID;
       
   114 
       
   115 	return names;
       
   116 }
       
   117 
       
   118 /**
       
   119  * Get the number of station classes in use.
       
   120  * @return Number of station classes.
       
   121  */
       
   122 uint GetNumFSMportsClasses()
       
   123 {
       
   124 	uint i;
       
   125 	for (i = 0; i < FSMPORTS_CLASS_MAX && fsmports_classes[i].id != 0; i++);
       
   126 	return i;
       
   127 }
       
   128 
       
   129 /**
       
   130  * Return the number of stations for the given station class.
       
   131  * @param sclass Index of the station class.
       
   132  * @return Number of stations in the class.
       
   133  */
       
   134 uint GetNumCustomFSMports(FSMportsClassID sclass)
       
   135 {
       
   136 	assert(sclass < FSMPORTS_CLASS_MAX);
       
   137 	return fsmports_classes[sclass].FSMports;
       
   138 }
       
   139 
       
   140 /**
       
   141  * Tie a fsmport spec to its class.
       
   142  * @param fsmportspec The station spec.
       
   143  */
       
   144 void SetCustomFSMportsSpec(FSMportsSpec *fsmportspec)
       
   145 {
       
   146 	FSMportsClass *fsmports_class;
       
   147 	int i;
       
   148 
       
   149 	/* If the station has already been allocated, don't reallocate it. */
       
   150 	if (fsmportspec->allocated) return;
       
   151 
       
   152 	assert(fsmportspec->sclass < FSMPORTS_CLASS_MAX);
       
   153 	fsmports_class = &fsmports_classes[fsmportspec->sclass];
       
   154 
       
   155 	i = fsmports_class->FSMports++;
       
   156 	fsmports_class->spec = ReallocT(fsmports_class->spec, fsmports_class->FSMports);
       
   157 
       
   158 	fsmports_class->spec[i] = fsmportspec;
       
   159 	fsmportspec->allocated = true;
       
   160 }
       
   161 
       
   162 /**
       
   163  * Retrieve a station spec from a class.
       
   164  * @param sclass Index of the station class.
       
   165  * @param station The station index with the class.
       
   166  * @return The station spec.
       
   167  */
       
   168 const FSMportsSpec *GetCustomFSMportsSpec(FSMportsClassID sclass, uint fsmport)
       
   169 {
       
   170 	assert(sclass < FSMPORTS_CLASS_MAX);
       
   171 	if (fsmport < fsmports_classes[sclass].FSMports)
       
   172 		return fsmports_classes[sclass].spec[fsmport];
       
   173 
       
   174 	/* If the custom station isn't defined any more, then the GRF file
       
   175 	 * probably was not loaded. */
       
   176 	return NULL;
       
   177 }
       
   178 
       
   179 
       
   180 const FSMportsSpec *GetCustomFSMportsSpecByGrf(uint32 grfid, byte localidx)
       
   181 {
       
   182 	uint j;
       
   183 
       
   184 	for (FSMportsClassID i = FSMPORTS_CLASS_BEGIN; i < FSMPORTS_CLASS_MAX; i++) {
       
   185 		for (j = 0; j < fsmports_classes[i].FSMports; j++) {
       
   186 			const FSMportsSpec *fsmportspec = fsmports_classes[i].spec[j];
       
   187 			if (fsmportspec == NULL) continue;
       
   188 			if (fsmportspec->grffile->grfid == grfid && fsmportspec->localidx == localidx) return fsmportspec;
       
   189 		}
       
   190 	}
       
   191 
       
   192 	return NULL;
       
   193 }
       
   194 
       
   195 
       
   196 /* FSMports Resolver Functions */
       
   197 static uint32 FSMportsGetRandomBits(const ResolverObject *object)
       
   198 {
       
   199 	const Station *st = object->u.station.st;
       
   200 	const TileIndex tile = object->u.station.tile;
       
   201 	return (st == NULL ? 0 : st->random_bits) | (tile == INVALID_TILE ? 0 : GetStationTileRandomBits(tile) << 16);
       
   202 }
       
   203 
       
   204 
       
   205 static uint32 FSMportsGetTriggers(const ResolverObject *object)
       
   206 {
       
   207 	const Station *st = object->u.station.st;
       
   208 	return st == NULL ? 0 : st->waiting_triggers;
       
   209 }
       
   210 
       
   211 
       
   212 static void FSMportsSetTriggers(const ResolverObject *object, int triggers)
       
   213 {
       
   214 	Station *st = (Station*)object->u.station.st;
       
   215 	assert(st != NULL);
       
   216 	st->waiting_triggers = triggers;
       
   217 }
       
   218 
       
   219 /**
       
   220  * FSMports variable cache
       
   221  * This caches 'expensive' station variable lookups which iterate over
       
   222  * several tiles that may be called multiple times per Resolve().
       
   223  */
       
   224 static struct {
       
   225 	uint32 v40;
       
   226 	uint32 v41;
       
   227 	uint32 v45;
       
   228 	uint32 v46;
       
   229 	uint32 v47;
       
   230 	uint32 v49;
       
   231 	uint8 valid;
       
   232 } _svc;
       
   233 
       
   234 static uint32 FSMportsGetVariable(const ResolverObject *object, byte variable, byte parameter, bool *available)
       
   235 {
       
   236 	const Station *st = object->u.station.st;
       
   237 	TileIndex tile = object->u.station.tile;
       
   238 
       
   239 	if (object->scope == VSG_SCOPE_PARENT) {
       
   240 		/* Pass the request on to the town of the station */
       
   241 		Town *t;
       
   242 
       
   243 		if (st != NULL) {
       
   244 			t = st->town;
       
   245 		} else if (tile != INVALID_TILE) {
       
   246 			t = GetTownByTile(tile);
       
   247 		} else {
       
   248 			*available = false;
       
   249 			return UINT_MAX;
       
   250 		}
       
   251 
       
   252 		return TownGetVariable(variable, parameter, available, t);
       
   253 	}
       
   254 
       
   255 	if (st == NULL) {
       
   256 		/* Station does not exist, so we're in a purchase list */
       
   257 		switch (variable) {
       
   258 			case 0x40:
       
   259 			case 0x41:
       
   260 			case 0x46:
       
   261 			case 0x47:
       
   262 			case 0x49: return 0x2110000;       // Platforms, tracks & position
       
   263 			case 0x42: return 0;               // Rail type (XXX Get current type from GUI?)
       
   264 			case 0x43: return _current_player; // Station owner
       
   265 			case 0x44: return 2;               // PBS status
       
   266 			case 0xFA: return clamp(_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0, 65535); // Build date, clamped to a 16 bit value
       
   267 		}
       
   268 
       
   269 		*available = false;
       
   270 		return UINT_MAX;
       
   271 	}
       
   272 
       
   273 	switch (variable) {
       
   274 		/* Calculated station variables */
       
   275 		case 0x42: return GetTerrainType(tile) | (GetRailType(tile) << 8);
       
   276 		case 0x43: return st->owner; // Station owner
       
   277 		case 0x48: { // Accepted cargo types
       
   278 			CargoID cargo_type;
       
   279 			uint32 value = 0;
       
   280 
       
   281 			for (cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) {
       
   282 				if (st->goods[cargo_type].acceptance) SETBIT(value, cargo_type);
       
   283 			}
       
   284 			return value;
       
   285 		}
       
   286 		/* Variables which use the parameter */
       
   287 		/* Variables 0x60 to 0x65 are handled separately below */
       
   288 
       
   289 		/* General station properties */
       
   290 		case 0x82: return 50;
       
   291 		case 0x84: return st->string_id;
       
   292 		case 0x86: return 0;
       
   293 		case 0x8A: return st->had_vehicle_of_type;
       
   294 		case 0xF0: return st->facilities;
       
   295 		case 0xF1: return st->airport_type;
       
   296 		case 0xF2: return st->truck_stops->status;
       
   297 		case 0xF3: return st->bus_stops->status;
       
   298 		case 0xF6: return st->airport_flags;
       
   299 		case 0xF7: return GB(st->airport_flags, 8, 8);
       
   300 		case 0xFA: return clamp(st->build_date - DAYS_TILL_ORIGINAL_BASE_YEAR, 0, 65535);
       
   301 	}
       
   302 
       
   303 	/* Handle cargo variables with parameter, 0x60 to 0x65 */
       
   304 	if (variable >= 0x60 && variable <= 0x65) {
       
   305 		CargoID c = GetCargoTranslation(parameter, object->u.station.statspec->grffile);
       
   306 
       
   307 		if (c == CT_INVALID) return 0;
       
   308 		const GoodsEntry *ge = &st->goods[c];
       
   309 
       
   310 		switch (variable) {
       
   311 			case 0x60: return min(ge->cargo.Count(), 4095);
       
   312 			case 0x61: return ge->days_since_pickup;
       
   313 			case 0x62: return ge->rating;
       
   314 			case 0x63: return ge->cargo.DaysInTransit();
       
   315 			case 0x64: return ge->last_speed | (ge->last_age << 8);
       
   316 			case 0x65: return ge->acceptance << 3;
       
   317 		}
       
   318 	}
       
   319 
       
   320 	/* Handle cargo variables (deprecated) */
       
   321 	if (variable >= 0x8C && variable <= 0xEC) {
       
   322 		const GoodsEntry *g = &st->goods[GB(variable - 0x8C, 3, 4)];
       
   323 		switch (GB(variable - 0x8C, 0, 3)) {
       
   324 			case 0: return g->cargo.Count();
       
   325 			case 1: return GB(min(g->cargo.Count(), 4095), 0, 4) | (g->acceptance << 7);
       
   326 			case 2: return g->days_since_pickup;
       
   327 			case 3: return g->rating;
       
   328 			case 4: return g->cargo.Source();
       
   329 			case 5: return g->cargo.DaysInTransit();
       
   330 			case 6: return g->last_speed;
       
   331 			case 7: return g->last_age;
       
   332 		}
       
   333 	}
       
   334 
       
   335 	DEBUG(grf, 1, "Unhandled station property 0x%X", variable);
       
   336 
       
   337 	*available = false;
       
   338 	return UINT_MAX;
       
   339 }
       
   340 
       
   341 
       
   342 static const SpriteGroup *FSMportsResolveReal(const ResolverObject *object, const SpriteGroup *group)
       
   343 {
       
   344 	const Station *st = object->u.station.st;
       
   345 	const FSMportsSpec *fsmportspec = object->u.station.fsmportspec;
       
   346 	uint set;
       
   347 
       
   348 	uint cargo = 0;
       
   349 	CargoID cargo_type = object->u.station.cargo_type;
       
   350 
       
   351 	if (st == NULL || fsmportspec->sclass == FSMPORTS_CLASS_WAYP) {
       
   352 		return group->g.real.loading[0];
       
   353 	}
       
   354 
       
   355 	switch (cargo_type) {
       
   356 		case CT_INVALID:
       
   357 		case CT_DEFAULT_NA:
       
   358 		case CT_PURCHASE:
       
   359 			cargo = 0;
       
   360 			break;
       
   361 
       
   362 		case CT_DEFAULT:
       
   363 			for (cargo_type = 0; cargo_type < NUM_CARGO; cargo_type++) {
       
   364 				cargo += st->goods[cargo_type].cargo.Count();
       
   365 			}
       
   366 			break;
       
   367 
       
   368 		default:
       
   369 			cargo = st->goods[cargo_type].cargo.Count();
       
   370 			break;
       
   371 	}
       
   372 
       
   373 	if (HASBIT(fsmportspec->flags, 1)) cargo /= (st->trainst_w + st->trainst_h);
       
   374 	cargo = min(0xfff, cargo);
       
   375 
       
   376 	if (cargo > fsmportspec->cargo_threshold) {
       
   377 		if (group->g.real.num_loading > 0) {
       
   378 			set = ((cargo - fsmportspec->cargo_threshold) * group->g.real.num_loading) / (4096 - fsmportspec->cargo_threshold);
       
   379 			return group->g.real.loading[set];
       
   380 		}
       
   381 	} else {
       
   382 		if (group->g.real.num_loaded > 0) {
       
   383 			set = (cargo * group->g.real.num_loaded) / (fsmportspec->cargo_threshold + 1);
       
   384 			return group->g.real.loaded[set];
       
   385 		}
       
   386 	}
       
   387 
       
   388 	return group->g.real.loading[0];
       
   389 }
       
   390 
       
   391 
       
   392 static void NewFSMportsResolver(ResolverObject *res, const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
       
   393 {
       
   394 	res->GetRandomBits = FSMportsGetRandomBits;
       
   395 	res->GetTriggers   = FSMportsGetTriggers;
       
   396 	res->SetTriggers   = FSMportsSetTriggers;
       
   397 	res->GetVariable   = FSMportsGetVariable;
       
   398 	res->ResolveReal   = FSMportsResolveReal;
       
   399 
       
   400 	res->u.station.st       = st;
       
   401 	res->u.station.fsmportspec = fsmportspec;
       
   402 	res->u.station.tile     = tile;
       
   403 
       
   404 	res->callback        = 0;
       
   405 	res->callback_param1 = 0;
       
   406 	res->callback_param2 = 0;
       
   407 	res->last_value      = 0;
       
   408 	res->trigger         = 0;
       
   409 	res->reseed          = 0;
       
   410 }
       
   411 
       
   412 static const SpriteGroup *ResolveFSMports(ResolverObject *object)
       
   413 {
       
   414 	const SpriteGroup *group;
       
   415 	CargoID ctype = CT_DEFAULT_NA;
       
   416 
       
   417 	if (object->u.station.st == NULL) {
       
   418 		/* No station, so we are in a purchase list */
       
   419 		ctype = CT_PURCHASE;
       
   420 	} else {
       
   421 		/* Pick the first cargo that we have waiting */
       
   422 		for (CargoID cargo = 0; cargo < NUM_CARGO; cargo++) {
       
   423 			const CargoSpec *cs = GetCargo(cargo);
       
   424 			if (cs->IsValid() && object->u.station.fsmportspec->spritegroup[cargo] != NULL &&
       
   425 					!object->u.station.st->goods[cargo].cargo.Empty()) {
       
   426 				ctype = cargo;
       
   427 				break;
       
   428 			}
       
   429 		}
       
   430 	}
       
   431 
       
   432 	group = object->u.station.fsmportspec->spritegroup[ctype];
       
   433 	if (group == NULL) {
       
   434 		ctype = CT_DEFAULT;
       
   435 		group = object->u.station.fsmportspec->spritegroup[ctype];
       
   436 	}
       
   437 
       
   438 	if (group == NULL) return NULL;
       
   439 
       
   440 	/* Remember the cargo type we've picked */
       
   441 	object->u.station.cargo_type = ctype;
       
   442 
       
   443 	/* Invalidate all cached vars */
       
   444 	_svc.valid = 0;
       
   445 
       
   446 	return Resolve(group, object);
       
   447 }
       
   448 
       
   449 SpriteID GetCustomFSMportsRelocation(const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
       
   450 {
       
   451 	const SpriteGroup *group;
       
   452 	ResolverObject object;
       
   453 
       
   454 	NewFSMportsResolver(&object, fsmportspec, st, tile);
       
   455 
       
   456 	group = ResolveFSMports(&object);
       
   457 	if (group == NULL || group->type != SGT_RESULT) return 0;
       
   458 	return group->g.result.sprite - 0x42D;
       
   459 }
       
   460 
       
   461 
       
   462 SpriteID GetCustomFSMportsGroundRelocation(const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
       
   463 {
       
   464 	const SpriteGroup *group;
       
   465 	ResolverObject object;
       
   466 
       
   467 	NewFSMportsResolver(&object, fsmportspec, st, tile);
       
   468 	object.callback_param1 = 1; // Indicate we are resolving the ground sprite
       
   469 
       
   470 	group = ResolveFSMports(&object);
       
   471 	if (group == NULL || group->type != SGT_RESULT) return 0;
       
   472 	return group->g.result.sprite - 0x42D;
       
   473 }
       
   474 
       
   475 
       
   476 uint16 GetFSMportsCallback(uint16 callback, uint32 param1, uint32 param2, const FSMportsSpec *fsmportspec, const Station *st, TileIndex tile)
       
   477 {
       
   478 	const SpriteGroup *group;
       
   479 	ResolverObject object;
       
   480 
       
   481 	NewFSMportsResolver(&object, fsmportspec, st, tile);
       
   482 
       
   483 	object.callback        = callback;
       
   484 	object.callback_param1 = param1;
       
   485 	object.callback_param2 = param2;
       
   486 
       
   487 	group = ResolveFSMports(&object);
       
   488 	if (group == NULL || group->type != SGT_CALLBACK) return CALLBACK_FAILED;
       
   489 	return group->g.callback.result;
       
   490 }
       
   491 
       
   492 
       
   493 /**
       
   494  * Allocate a FSMportsSpec to a Station. This is called once per build operation.
       
   495  * @param statspec FSMportsSpec to allocate.
       
   496  * @param st Station to allocate it to.
       
   497  * @param exec Whether to actually allocate the spec.
       
   498  * @return Index within the FSMports's spec list, or -1 if the allocation failed.
       
   499  */
       
   500 int AllocateFSMportsSpecToStation(const FSMportsSpec *fsmportspec, Station *st, bool exec)
       
   501 {
       
   502 	uint i;
       
   503 
       
   504 	if (fsmportspec == NULL) return 0;
       
   505 
       
   506 	/* Check if this spec has already been allocated */
       
   507 	for (i = 1; i < st->num_fsmportsspecs && i < MAX_SPECLIST; i++) {
       
   508 		if (st->fsmportsspeclist[i].spec == fsmportspec) return i;
       
   509 	}
       
   510 
       
   511 	for (i = 1; i < st->num_fsmportsspecs && i < MAX_SPECLIST; i++) {
       
   512 		if (st->fsmportsspeclist[i].spec == NULL && st->fsmportsspeclist[i].grfid == 0) break;
       
   513 	}
       
   514 
       
   515 	if (i == MAX_SPECLIST) return -1;
       
   516 
       
   517 	if (exec) {
       
   518 		if (i >= st->num_fsmportsspecs) {
       
   519 			st->num_fsmportsspecs = i + 1;
       
   520 			st->fsmportsspeclist = ReallocT(st->fsmportsspeclist, st->num_fsmportsspecs);
       
   521 
       
   522 			if (st->num_fsmportsspecs == 2) {
       
   523 				/* Initial allocation */
       
   524 				st->fsmportsspeclist[0].spec     = NULL;
       
   525 				st->fsmportsspeclist[0].grfid    = 0;
       
   526 				st->fsmportsspeclist[0].localidx = 0;
       
   527 			}
       
   528 		}
       
   529 
       
   530 		st->fsmportsspeclist[i].spec     = fsmportspec;
       
   531 		st->fsmportsspeclist[i].grfid    = fsmportspec->grffile->grfid;
       
   532 		st->fsmportsspeclist[i].localidx = fsmportspec->localidx;
       
   533 	}
       
   534 
       
   535 	return i;
       
   536 }
       
   537 
       
   538 
       
   539 /** Deallocate a FSMportsSpec from a Station. Called when removing a single station tile.
       
   540  * @param st Station to work with.
       
   541  * @param specindex Index of the custom station within the FSMports's spec list.
       
   542  * @return Indicates whether the FSMportsSpec was deallocated.
       
   543  */
       
   544 void DeallocateSpecFromFSMports(Station* st, byte specindex)
       
   545 {
       
   546 	/* specindex of 0 (default) is never freeable */
       
   547 	if (specindex == 0) return;
       
   548 
       
   549 	/* Check all tiles over the station to check if the specindex is still in use */
       
   550 	BEGIN_TILE_LOOP(tile, st->trainst_w, st->trainst_h, st->train_tile) {
       
   551 		if (IsTileType(tile, MP_STATION) && GetStationIndex(tile) == st->index && GetCustomStationSpecIndex(tile) == specindex) {
       
   552 			return;
       
   553 		}
       
   554 	} END_TILE_LOOP(tile, st->trainst_w, st->trainst_h, st->train_tile)
       
   555 
       
   556 	/* This specindex is no longer in use, so deallocate it */
       
   557 	st->speclist[specindex].spec     = NULL;
       
   558 	st->speclist[specindex].grfid    = 0;
       
   559 	st->speclist[specindex].localidx = 0;
       
   560 
       
   561 	/* If this was the highest spec index, reallocate */
       
   562 	if (specindex == st->num_specs - 1) {
       
   563 		for (; st->speclist[st->num_specs - 1].grfid == 0 && st->num_specs > 1; st->num_specs--);
       
   564 
       
   565 		if (st->num_specs > 1) {
       
   566 			st->speclist = ReallocT(st->speclist, st->num_specs);
       
   567 		} else {
       
   568 			free(st->speclist);
       
   569 			st->num_specs = 0;
       
   570 			st->speclist  = NULL;
       
   571 		}
       
   572 	}
       
   573 }
       
   574 
       
   575 /** Draw representation of a station tile for GUI purposes.
       
   576  * @param x Position x of image.
       
   577  * @param y Position y of image.
       
   578  * @param axis Axis.
       
   579  * @param railtype Rail type.
       
   580  * @param sclass, station Type of station.
       
   581  * @param station station ID
       
   582  * @return True if the tile was drawn (allows for fallback to default graphic)
       
   583  */
       
   584 bool DrawFSMportsTile(int x, int y, RailType railtype, Axis axis, FSMportsClassID sclass, uint station)
       
   585 {
       
   586 	const FSMportsSpec *fsmportsspec;
       
   587 	const DrawTileSprites *sprites;
       
   588 	const DrawTileSeqStruct *seq;
       
   589 	const RailtypeInfo *rti = GetRailTypeInfo(railtype);
       
   590 	SpriteID relocation;
       
   591 	SpriteID image;
       
   592 	SpriteID pal = PLAYER_SPRITE_COLOR(_local_player);
       
   593 	uint tile = 2;
       
   594 
       
   595 	fsmportsspec = GetCustomFSMportsSpec(sclass, station);
       
   596 	if (fsmportsspec == NULL) return false;
       
   597 
       
   598 	relocation = GetCustomFSMportsRelocation(fsmportsspec, NULL, INVALID_TILE);
       
   599 
       
   600 	if (HASBIT(fsmportsspec->callbackmask, CBM_CUSTOM_LAYOUT)) {
       
   601 		uint16 callback = GetFSMportsCallback(CBID_STATION_SPRITE_LAYOUT, 0x2110000, 0, fsmportsspec, NULL, INVALID_TILE);
       
   602 		if (callback != CALLBACK_FAILED) tile = callback;
       
   603 	}
       
   604 
       
   605 	if (fsmportsspec->renderdata == NULL) {
       
   606 		sprites = GetStationTileLayout(tile + axis);
       
   607 	} else {
       
   608 		sprites = &fsmportsspec->renderdata[(tile < fsmportsspec->tiles) ? tile + axis : (uint)axis];
       
   609 	}
       
   610 
       
   611 	image = sprites->ground_sprite;
       
   612 	if (HASBIT(image, SPRITE_MODIFIER_USE_OFFSET)) {
       
   613 		image += GetCustomFSMportsGroundRelocation(fsmportsspec, NULL, INVALID_TILE);
       
   614 		image += rti->custom_ground_offset;
       
   615 	} else {
       
   616 		image += rti->total_offset;
       
   617 	}
       
   618 
       
   619 	DrawSprite(image, PAL_NONE, x, y);
       
   620 
       
   621 	foreach_draw_tile_seq(seq, sprites->seq) {
       
   622 		Point pt;
       
   623 		image = seq->image;
       
   624 		if (HASBIT(image, SPRITE_MODIFIER_USE_OFFSET)) {
       
   625 			image += rti->total_offset;
       
   626 		} else {
       
   627 			image += relocation;
       
   628 		}
       
   629 
       
   630 		if ((byte)seq->delta_z != 0x80) {
       
   631 			pt = RemapCoords(seq->delta_x, seq->delta_y, seq->delta_z);
       
   632 			DrawSprite(image, pal, x + pt.x, y + pt.y);
       
   633 		}
       
   634 	}
       
   635 
       
   636 	return true;
       
   637 }
       
   638 
       
   639 
       
   640 static const FSMportsSpec* GetFSMportsSpec(TileIndex t)
       
   641 {
       
   642 	const Station* st;
       
   643 	uint specindex;
       
   644 
       
   645 	if (!IsCustomStationSpecIndex(t)) return NULL;
       
   646 
       
   647 	st = GetStationByTile(t);
       
   648 	specindex = GetCustomStationSpecIndex(t);
       
   649 	return specindex < st->num_fsmportsspecs ? st->fsmportsspeclist[specindex].spec : NULL;
       
   650 	//TODO: expect quirky behaviour here. specindex is one var, but pointing into two speclists... need to resolve which
       
   651 }
       
   652