(svn r581) -newgrf: Basic support for new stations - only waypoints supported now and only
authorcelestar
Sun, 14 Nov 2004 09:07:15 +0000
changeset 389 16ee9854091e
parent 388 964d8c8d89ea
child 390 c67581486cde
(svn r581) -newgrf: Basic support for new stations - only waypoints supported now and only
the first custom one can be placed (no selector GUI, coming soon). This
also moves some global variables to {struct GRFFile} and reorders which
actions are processed in what stage, to get it all working together --
(pasky)
docs/landscape.html
engine.h
grfspecial.c
rail_cmd.c
rail_gui.c
station.h
station_cmd.c
--- a/docs/landscape.html	Sun Nov 14 08:15:50 2004 +0000
+++ b/docs/landscape.html	Sun Nov 14 09:07:15 2004 +0000
@@ -135,6 +135,8 @@
 <br>
 <li>map_owner: <a href="#OwnershipInfo">owner</a> of the depot / checkpoint</li>
 <li>map3_lo bits 0..3 = <a href="#TrackType">track type</a></li>
+<li>map3_lo bit 4 = use custom sprite (valid only for the checkpoint)</li>
+<li>map3_hi = custom station id</li>
 </ul>
 </td></tr>
 
--- a/engine.h	Sun Nov 14 08:15:50 2004 +0000
+++ b/engine.h	Sun Nov 14 09:07:15 2004 +0000
@@ -84,8 +84,14 @@
 	// XXX: Would anyone ever need more than 16 spritesets? Maybe we should
 	// use even less, now we take whole 8kb for custom sprites table, oh my!
 	byte sprites_per_set; // means number of directions - 4 or 8
+
 	// Loaded = in motion, loading = not moving
 	// Each group contains several spritesets, for various loading stages
+
+	// XXX: For stations the meaning is different - loaded is for stations
+	// with small amount of cargo whilst loading is for stations with a lot
+	// of da stuff.
+
 	byte loaded_count;
 	uint16 loaded[16]; // sprite ids
 	byte loading_count;
--- a/grfspecial.c	Sun Nov 14 08:15:50 2004 +0000
+++ b/grfspecial.c	Sun Nov 14 09:07:15 2004 +0000
@@ -6,6 +6,7 @@
 #include "gfx.h"
 #include "fileio.h"
 #include "engine.h"
+#include "station.h"
 
 /* TTDPatch extended GRF format codec
  * (c) Petr Baudis 2004 (GPL'd)
@@ -26,6 +27,29 @@
 	uint16 flags;
 	uint16 sprite_offset;
 	struct GRFFile *next;
+
+	/* A sprite group contains all sprites of a given vehicle (or multiple
+	 * vehicles) when carrying given cargo. It consists of several sprite
+	 * sets.  Group ids are refered as "cargo id"s by TTDPatch
+	 * documentation, contributing to the global confusion.
+	 *
+	 * A sprite set contains all sprites of a given vehicle carrying given
+	 * cargo at a given *stage* - that is usually its load stage. Ie. you
+	 * can have a spriteset for an empty wagon, wagon full of coal,
+	 * half-filled wagon etc.  Each spriteset contains eight sprites (one
+	 * per direction) or four sprites if the vehicle is symmetric. */
+
+	int spriteset_start;
+	int spriteset_numsets;
+	int spriteset_numents;
+	int spriteset_feature;
+
+	int spritesset_count;
+	struct SpriteGroup *spritesset;
+
+	uint32 statinfo_classid[256];
+	byte statinfo_tiles[256];
+	DrawTileSprites statinfo_renderdata[256][8];
 };
 
 static struct GRFFile *_cur_grffile, *_first_grffile;
@@ -664,8 +688,178 @@
 	return ret;
 }
 
+static bool StationChangeInfo(uint stid, int numinfo, int prop, byte **bufp, int len)
+{
+	byte *buf = *bufp;
+	int i;
+	int ret = 0;
+
+	/* This is one single huge TODO. It doesn't handle anything more than
+	 * just waypoints for now. */
+
+	//printf("sci %d %d [0x%02x]\n", stid, numinfo, prop);
+	switch (prop) {
+		case 0x08:
+		{	/* Class ID */
+			FOR_EACH_ENGINE {
+				/* classid, for a change, is always little-endian */
+				_cur_grffile->statinfo_classid[stid+i] = *(buf++) << 24;
+				_cur_grffile->statinfo_classid[stid+i] |= *(buf++) << 16;
+				_cur_grffile->statinfo_classid[stid+i] |= *(buf++) << 8;
+				_cur_grffile->statinfo_classid[stid+i] |= *(buf++);
+			}
+			break;
+		}
+		case 0x09:
+		{	/* Define sprite layout */
+			FOR_EACH_ENGINE {
+				int t;
+				_cur_grffile->statinfo_tiles[stid+i] = grf_load_byte(&buf);
+				for (t = 0; t < _cur_grffile->statinfo_tiles[stid+i]; t++) {
+					DrawTileSprites *dts = &_cur_grffile->statinfo_renderdata[stid+i][t];
+					int seq_count = 0;
+
+					if (t >= 8) {
+						grfmsg(GMS_WARN, "StationChangeInfo: Sprite %d>=8, skipping.", t);
+						grf_load_dword(&buf); // at least something
+						continue;
+					}
+
+					dts->ground_sprite = grf_load_dword(&buf);
+					if (!dts->ground_sprite) {
+						static const DrawTileSeqStruct empty = {0x80};
+						dts->seq = &empty;
+						continue;
+					}
+
+					dts->seq = NULL;
+					while (buf < *bufp + len) {
+						DrawTileSeqStruct *dtss;
+
+						// no relative bounding box support
+						dts->seq = realloc((void*)dts->seq, ++seq_count * sizeof(DrawTileSeqStruct));
+						dtss = (DrawTileSeqStruct*) &dts->seq[seq_count - 1];
+
+						dtss->delta_x = grf_load_byte(&buf);
+						if ((byte) dtss->delta_x == 0x80) break;
+						dtss->delta_y = grf_load_byte(&buf);
+						dtss->delta_z = grf_load_byte(&buf);
+						dtss->width = grf_load_byte(&buf);
+						dtss->height = grf_load_byte(&buf);
+						dtss->unk = grf_load_byte(&buf);
+						dtss->image = grf_load_dword(&buf) - 0x42d;
+					}
+				}
+			}
+			break;
+		}
+		case 0x0a:
+		{	/* Copy sprite layout */
+			FOR_EACH_ENGINE {
+				byte src = grf_load_byte(&buf);
+				int t;
+				_cur_grffile->statinfo_tiles[stid+i] = _cur_grffile->statinfo_tiles[src];
+				for (t = 0; t < _cur_grffile->statinfo_tiles[stid+i]; t++) {
+					DrawTileSprites *dts = &_cur_grffile->statinfo_renderdata[stid+i][t];
+					DrawTileSprites *sdts = &_cur_grffile->statinfo_renderdata[src][t];
+					DrawTileSeqStruct const *sdtss = sdts->seq;
+					int seq_count = 0;
+
+					dts->ground_sprite = sdts->ground_sprite;
+					if (!dts->ground_sprite) {
+						static const DrawTileSeqStruct empty = {0x80};
+						dts->seq = &empty;
+						continue;
+					}
+
+					dts->seq = NULL;
+					while (1) {
+						DrawTileSeqStruct *dtss;
+
+						// no relative bounding box support
+						dts->seq = realloc((void*)dts->seq, ++seq_count * sizeof(DrawTileSeqStruct));
+						dtss = (DrawTileSeqStruct*) &dts->seq[seq_count - 1];
+						*dtss = *sdtss;
+						if ((byte) dtss->delta_x == 0x80) break;
+						sdtss++;
+					}
+				}
+			}
+			break;
+		}
+		case 0x0b:
+		{	/* Callback */
+			/* TODO */
+			FOR_EACH_ENGINE {
+				grf_load_byte(&buf);
+			}
+			ret = 1;
+			break;
+		}
+		case 0x0c:
+		{	/* Platforms number */
+			/* TODO */
+			FOR_EACH_ENGINE {
+				grf_load_byte(&buf);
+			}
+			ret = 1;
+			break;
+		}
+		case 0x0d:
+		{	/* Platforms length */
+			/* TODO */
+			FOR_EACH_ENGINE {
+				grf_load_byte(&buf);
+			}
+			ret = 1;
+			break;
+		}
+		case 0x0e:
+		{	/* Define custom layout */
+			/* TODO */
+			FOR_EACH_ENGINE {
+				while (buf < *bufp + len) {
+					byte length = grf_load_byte(&buf);
+					byte number = grf_load_byte(&buf);
+					int k = length * number;
+
+					if (!length && !number) break;
+					while (k--) grf_load_byte(&buf);
+				}
+			}
+			ret = 1;
+			break;
+		}
+		case 0x0f:
+		{	/* Copy custom layout */
+			/* TODO */
+			FOR_EACH_ENGINE {
+				grf_load_byte(&buf);
+			}
+			ret = 1;
+			break;
+		}
+		case 0x10:
+		{	/* Little/lots cargo threshold */
+			/* TODO */
+			FOR_EACH_ENGINE {
+				grf_load_word(&buf);
+			}
+			ret = 1;
+			break;
+		}
+		default:
+			ret = 1;
+			break;
+	}
+
+	*bufp = buf;
+	return ret;
+}
+
 #undef shift_buf
 
+
 /* Action 0x00 */
 static void VehicleChangeInfo(byte *buf, int len)
 {
@@ -690,7 +884,7 @@
 		/* GSF_ROAD */     RoadVehicleChangeInfo,
 		/* GSF_SHIP */     ShipVehicleChangeInfo,
 		/* GSF_AIRCRAFT */ AircraftVehicleChangeInfo,
-		/* GSF_STATION */  NULL,
+		/* GSF_STATION */  StationChangeInfo,
 	};
 
 	uint8 feature;
@@ -708,18 +902,18 @@
 	DEBUG(grf, 6) ("VehicleChangeInfo: Feature %d, %d properties, to apply to %d+%d",
 	               feature, numprops, engine, numinfo);
 
-	if (feature == GSF_STATION) {
-		grfmsg(GMS_WARN, "VehicleChangeInfo: Stations unsupported, skipping.");
-		return;
-	}
-
-	ei = &_engine_info[engine + _vehshifts[feature]];
+	if (feature != GSF_STATION)
+		ei = &_engine_info[engine + _vehshifts[feature]];
 
 	buf += 5;
 
 	while (numprops-- && buf < bufend) {
 		uint8 prop = grf_load_byte(&buf);
 
+		if (feature == 4)
+			// stations don't share those common properties
+			goto run_handler;
+
 		switch (prop) {
 		case 0x00: { /* Introduction date */
 			FOR_EACH_ENGINE {
@@ -771,6 +965,7 @@
 			goto ignoring;
 		}
 		default: {
+run_handler:
 			if (handler[feature](engine, numinfo, prop, &buf, bufend - buf)) {
 ignoring:
 				grfmsg(GMS_NOTICE, "VehicleChangeInfo: Ignoring property %x (not implemented).", prop);
@@ -783,25 +978,6 @@
 }
 
 
-/* A sprite group contains all sprites of a given vehicle (or multiple
- * vehicles) when carrying given cargo. It consists of several sprite sets.
- * Group ids are refered as "cargo id"s by TTDPatch documentation,
- * contributing to the global confusion.
- *
- * A sprite set contains all sprites of a given vehicle carrying given cargo at
- * a given *stage* - that is usually its load stage. Ie. you can have a
- * spriteset for an empty wagon, wagon full of coal, half-filled wagon etc.
- * Each spriteset contains eight sprites (one per direction) or four sprites if
- * the vehicle is symmetric. */
-
-static int _spriteset_start;
-static int _spriteset_numsets;
-static int _spriteset_numents;
-static int _spriteset_feature;
-
-static int _spritesset_count;
-static struct SpriteGroup *_spritesset;
-
 /* Action 0x01 */
 static void NewSpriteSet(byte *buf, int len)
 {
@@ -823,16 +999,10 @@
 	check_length(len, 4, "NewSpriteSet");
 	feature = buf[1];
 
-	if (feature == GSF_STATION) {
-		_spriteset_start = 0;
-		grfmsg(GMS_WARN, "NewSpriteSet: Stations unsupported, skipping.");
-		return;
-	}
-
-	_spriteset_start = _cur_spriteid + 1;
-	_spriteset_feature = feature;
-	_spriteset_numsets = buf[2];
-	_spriteset_numents = buf[3];
+	_cur_grffile->spriteset_start = _cur_spriteid + 1;
+	_cur_grffile->spriteset_feature = feature;
+	_cur_grffile->spriteset_numsets = buf[2];
+	_cur_grffile->spriteset_numents = buf[3];
 }
 
 /* Action 0x02 */
@@ -850,12 +1020,13 @@
 	 *                 otherwise it specifies a number of entries, the exact
 	 *                 meaning depends on the feature
 	 * V feature-specific-data (huge mess, don't even look it up --pasky) */
-	/* TODO: Only trains supported now. No 0x80-types (ugh). */
+	/* TODO: No 0x80-types (ugh). */
 	/* TODO: Also, empty sprites aren't handled for now. Need to investigate
 	 * the "opacity" rules for these, that is which sprite to fall back to
 	 * when. --pasky */
 	uint8 feature;
 	uint8 setid;
+	/* XXX: For stations, these two are "little cargo" and "lotsa cargo" sets. */
 	uint8 numloaded;
 	uint8 numloading;
 	struct SpriteGroup *group;
@@ -869,19 +1040,9 @@
 	numloaded = buf[3];
 	numloading = buf[4];
 
-	if (feature == GSF_STATION) {
-		grfmsg(GMS_WARN, "NewSpriteGroup: Stations unsupported, skipping.");
-		return;
-	}
-
 	if (numloaded == 0x81) {
-		// XXX: This is _VERY_ ad hoc just to handle Dm3. And that is
-		// a semi-futile ask because the great Patchman himself says
-		// this is just buggy. It dereferences last (first) byte of
-		// a schedule list pointer of the vehicle and if it's 0xff
-		// it uses group 01, otherwise it uses group 00. Now
-		// if _you_ understand _that_... We just assume it is never
-		// 0xff and therefore go for group 00. --pasky
+		/* XXX: This just goes for the default superset for now,
+		 * straight and safe. --pasky */
 		uint8 var = buf[4];
 		//uint8 shiftnum = buf[5];
 		//uint8 andmask = buf[6];
@@ -893,14 +1054,13 @@
 
 		//val = (0xff << shiftnum) & andmask;
 
-		//Go for the default.
-		if (setid >= _spritesset_count) {
-			_spritesset_count = setid + 1;
-			_spritesset = realloc(_spritesset, _spritesset_count * sizeof(struct SpriteGroup));
+		if (setid >= _cur_grffile->spritesset_count) {
+			_cur_grffile->spritesset_count = setid + 1;
+			_cur_grffile->spritesset = realloc(_cur_grffile->spritesset, _cur_grffile->spritesset_count * sizeof(struct SpriteGroup));
 		}
 		buf += 8 + nvar * 4;
 		def = grf_load_word(&buf);
-		_spritesset[setid] = _spritesset[def];
+		_cur_grffile->spritesset[setid] = _cur_grffile->spritesset[def];
 		return;
 
 	} else if (numloaded & 0x80) {
@@ -908,14 +1068,14 @@
 		return;
 	}
 
-	if (!_spriteset_start) {
+	if (!_cur_grffile->spriteset_start) {
 		grfmsg(GMS_ERROR, "NewSpriteGroup: No sprite set to work on! Skipping.");
 		return;
 	}
 
-	if (_spriteset_feature != feature) {
-		grfmsg(GMS_ERROR, "NewSpriteGroup: Group feature %x doesn't match set feature %x! Skipping.", feature, _spriteset_feature);
-		return;
+	if (_cur_grffile->spriteset_feature != feature) {
+		grfmsg(GMS_ERROR, "NewSpriteGroup: Group feature %x doesn't match set feature %x! Playing it risky and trying to use it anyway.", feature, _cur_grffile->spriteset_feature);
+		// return; // XXX: we can't because of MB's newstats.grf --pasky
 	}
 
 	check_length(bufend - buf, 5, "NewSpriteGroup");
@@ -933,30 +1093,30 @@
 		numloading = 16;
 	}
 
-	if (setid >= _spritesset_count) {
-		_spritesset_count = setid + 1;
-		_spritesset = realloc(_spritesset, _spritesset_count * sizeof(struct SpriteGroup));
+	if (setid >= _cur_grffile->spritesset_count) {
+		_cur_grffile->spritesset_count = setid + 1;
+		_cur_grffile->spritesset = realloc(_cur_grffile->spritesset, _cur_grffile->spritesset_count * sizeof(struct SpriteGroup));
 	}
-	group = &_spritesset[setid];
+	group = &_cur_grffile->spritesset[setid];
 	memset(group, 0, sizeof(struct SpriteGroup));
-	group->sprites_per_set = _spriteset_numents;
+	group->sprites_per_set = _cur_grffile->spriteset_numents;
 	group->loaded_count  = numloaded;
 	group->loading_count = numloading;
 
 	DEBUG(grf, 6) ("NewSpriteGroup: New SpriteGroup 0x%02hhx, %u views, %u loaded, %u loading, sprites %u - %u",
 			setid, group->sprites_per_set, group->loaded_count, group->loading_count,
-			_spriteset_start - _cur_grffile->sprite_offset,
-			_spriteset_start + (_spriteset_numents * (numloaded + numloading)) - _cur_grffile->sprite_offset);
+			_cur_grffile->spriteset_start - _cur_grffile->sprite_offset,
+			_cur_grffile->spriteset_start + (_cur_grffile->spriteset_numents * (numloaded + numloading)) - _cur_grffile->sprite_offset);
 
 	for (i = 0; i < numloaded; i++) {
 		uint16 spriteset_id = grf_load_word(&loaded_ptr);
-		group->loaded[i] = _spriteset_start + spriteset_id * _spriteset_numents;
+		group->loaded[i] = _cur_grffile->spriteset_start + spriteset_id * _cur_grffile->spriteset_numents;
 		DEBUG(grf, 8) ("NewSpriteGroup: + group->loaded[%i]  = %u (subset %u)", i, group->loaded[i], spriteset_id);
 	}
 
 	for (i = 0; i < numloading; i++) {
 		uint16 spriteset_id = grf_load_word(&loading_ptr);
-		group->loading[i] = _spriteset_start + spriteset_id * _spriteset_numents;
+		group->loading[i] = _cur_grffile->spriteset_start + spriteset_id * _cur_grffile->spriteset_numents;
 		DEBUG(grf, 8) ("NewSpriteGroup: + group->loading[%i] = %u (subset %u)", i, group->loading[i], spriteset_id);
 	}
 }
@@ -1000,11 +1160,43 @@
 	DEBUG(grf, 6) ("VehicleMapSpriteGroup: Feature %d, %d ids, %d cids, wagon override %d.",
 			feature, idcount, cidcount, wagover);
 
+
 	if (feature == GSF_STATION) {
-		grfmsg(GMS_WARN, "VehicleMapSpriteGroup: Stations unsupported, skipping.");
+		// We do things differently for stations.
+
+		/* XXX: Currently we don't support cargo-specific images, so
+		 * we go straight to the defaults. */
+		byte *bp = buf + 4 + idcount + cidcount * 3;
+		uint16 groupid = grf_load_word(&bp);
+
+		for (i = 0; i < idcount; i++) {
+			uint8 stid = buf[3 + i];
+			int j;
+
+			if (groupid >= _cur_grffile->spritesset_count) {
+				grfmsg(GMS_WARN, "VehicleMapSpriteGroup: Spriteset %x out of range %x, skipping.",
+				       groupid, _cur_grffile->spritesset_count);
+				return;
+			}
+
+			// relocate sprite indexes based on spriteset locations
+			for (j = 0; j < _cur_grffile->statinfo_tiles[stid]; j++) {
+				DrawTileSeqStruct *seq;
+
+				foreach_draw_tile_seq(seq, (DrawTileSeqStruct*) _cur_grffile->statinfo_renderdata[stid][j].seq) {
+					seq->image += _cur_grffile->spritesset[groupid].loading[0];
+				}
+			}
+			/* FIXME: This means several GRF files defining new stations
+			 * will override each other, but the stid should be GRF-specific
+			 * instead! --pasky */
+			SetCustomStation(_cur_grffile->statinfo_classid[stid], stid, _cur_grffile->statinfo_renderdata[stid], _cur_grffile->statinfo_tiles[stid]);
+			_cur_grffile->statinfo_classid[stid] = 0;
+		}
 		return;
 	}
 
+
 	/* If ``n-id'' (or ``idcount'') is zero, this is a ``feature
 	 * callback''. I have no idea how this works, so we will ignore it for
 	 * now.  --octo */
@@ -1017,7 +1209,7 @@
 	// 03 00 01 19 01 00 00 00 00 - this is missing one 00 at the end,
 	// what should we exactly do with that? --pasky
 
-	if (!_spriteset_start || !_spritesset) {
+	if (!_cur_grffile->spriteset_start || !_cur_grffile->spritesset) {
 		grfmsg(GMS_WARN, "VehicleMapSpriteGroup: No sprite set to work on! Skipping.");
 		return;
 	}
@@ -1056,8 +1248,8 @@
 
 			DEBUG(grf, 8) ("VehicleMapSpriteGroup: * [%d] Cargo type %x, group id %x", c, ctype, groupid);
 
-			if (groupid >= _spritesset_count) {
-				grfmsg(GMS_WARN, "VehicleMapSpriteGroup: Spriteset %x out of range %x, skipping.", groupid, _spritesset_count);
+			if (groupid >= _cur_grffile->spritesset_count) {
+				grfmsg(GMS_WARN, "VehicleMapSpriteGroup: Spriteset %x out of range %x, skipping.", groupid, _cur_grffile->spritesset_count);
 				return;
 			}
 
@@ -1066,9 +1258,9 @@
 
 			if (wagover) {
 				// TODO: No multiple cargo types per vehicle yet. --pasky
-				SetWagonOverrideSprites(engine, &_spritesset[groupid], last_engines, last_engines_count);
+				SetWagonOverrideSprites(engine, &_cur_grffile->spritesset[groupid], last_engines, last_engines_count);
 			} else {
-				SetCustomEngineSprites(engine, ctype, &_spritesset[groupid]);
+				SetCustomEngineSprites(engine, ctype, &_cur_grffile->spritesset[groupid]);
 				last_engines[i] = engine;
 			}
 		}
@@ -1084,16 +1276,16 @@
 			uint8 engine = buf[3 + i] + _vehshifts[feature];
 
 			// Don't tell me you don't love duplicated code!
-			if (groupid >= _spritesset_count) {
-				grfmsg(GMS_WARN, "VehicleMapSpriteGroup: Spriteset %x out of range %x, skipping.", groupid, _spritesset_count);
+			if (groupid >= _cur_grffile->spritesset_count) {
+				grfmsg(GMS_WARN, "VehicleMapSpriteGroup: Spriteset %x out of range %x, skipping.", groupid, _cur_grffile->spritesset_count);
 				return;
 			}
 
 			if (wagover) {
 				// TODO: No multiple cargo types per vehicle yet. --pasky
-				SetWagonOverrideSprites(engine, &_spritesset[groupid], last_engines, last_engines_count);
+				SetWagonOverrideSprites(engine, &_cur_grffile->spritesset[groupid], last_engines, last_engines_count);
 			} else {
-				SetCustomEngineSprites(engine, CID_DEFAULT, &_spritesset[groupid]);
+				SetCustomEngineSprites(engine, CID_DEFAULT, &_cur_grffile->spritesset[groupid]);
 				last_engines[i] = engine;
 			}
 		}
@@ -1116,6 +1308,7 @@
 	 * (completely new scenarios changing all graphics and logically also
 	 * factory names etc). We should then also support all languages (by
 	 * name), not only the original four ones. --pasky */
+	/* TODO: Support for custom station class/type names. */
 
 	uint8 feature;
 	uint8 lang;
@@ -1577,16 +1770,13 @@
 {
 	struct GRFFile *newfile;
 
-	newfile = malloc(sizeof(struct GRFFile));
+	newfile = calloc(1, sizeof(struct GRFFile));
 
 	if (newfile == NULL)
 		error ("Out of memory");
 
 	newfile->filename = strdup(filename);
-	newfile->grfid = 0;
-	newfile->flags = 0x0000;
 	newfile->sprite_offset = sprite_offset;
-	newfile->next = NULL;
 
 	if (_first_grffile == NULL) {
 		_cur_grffile = newfile;
@@ -1645,15 +1835,21 @@
 
 	action = buf[0];
 
-	/* XXX: Action 0x03 is temporarily processed together with actions 0x01
-	 * and 0x02 before it is fixed to be reentrant (probably storing the
-	 * group information in {struct GRFFile}). --pasky */
+	/* XXX: There is a difference between staged loading in TTDPatch and
+	 * here.  In TTDPatch, for some reason actions 1 and 2 are carried out
+	 * during stage 0, whilst action 3 is carried out during stage 1 (to
+	 * "resolve" cargo IDs... wtf). This is a little problem, because cargo
+	 * IDs are valid only within a given set (action 1) block, and may be
+	 * overwritten after action 3 associates them. But overwriting happens
+	 * in an earlier stage than associating, so...  We just process actions
+	 * 1 and 2 in stage 1 now, let's hope that won't get us into problems.
+	 * --pasky */
 
 	if (stage == 0) {
-		/* During initialization, actions 0, 3, 4, 5 and 7 are ignored. */
+		/* During initialization, actions 0, 1, 2, 3, 4, 5 and 7 are ignored. */
 
-		if ((action == 0x00) /*|| (action == 0x03)*/ || (action == 0x04)
-		    || (action == 0x05) || (action == 0x07)) {
+		if ((action == 0x00) || (action == 0x01) || (action == 0x02) || (action == 0x03)
+		    || (action == 0x04) || (action == 0x05) || (action == 0x07)) {
 			DEBUG (grf, 7) ("DecodeSpecialSprite: Action: %x, Stage 0, Skipped", action);
 			/* Do nothing. */
 
@@ -1672,7 +1868,7 @@
 		 * considered active if its action 8 has been processed, i.e. its
 		 * action 8 hasn't been skipped using an action 7.
 		 *
-		 * During activation, only actions 0, 3, 4, 5, 7, 8, 9 and 0A are
+		 * During activation, only actions 0, 1, 2, 3, 4, 5, 7, 8, 9 and 0A are
 		 * carried out.  All others are ignored, because they only need to be
 		 * processed once at initialization.  */
 
@@ -1686,8 +1882,9 @@
 			DEBUG (grf, 7) ("DecodeSpecialSprite: Action: %x, Stage 1, Not activated", action);
 			/* Do nothing. */
 
-		} else if ((action == 0x00) /*|| (action == 0x03)*/ || (action == 0x04) || (action == 0x05)
-		           || (action == 0x07) || (action == 0x08) || (action == 0x09) || (action == 0x0A)) {
+		} else if ((action == 0x00) || (action == 0x01) || (action == 0x02) || (action == 0x03)
+			   || (action == 0x04) || (action == 0x05) || (action == 0x07) || (action == 0x08)
+			   || (action == 0x09) || (action == 0x0A)) {
 			DEBUG (grf, 7) ("DecodeSpecialSprite: Action: %x, Stage 1", action);
 			handlers[action](buf, num);
 
--- a/rail_cmd.c	Sun Nov 14 08:15:50 2004 +0000
+++ b/rail_cmd.c	Sun Nov 14 09:07:15 2004 +0000
@@ -6,6 +6,7 @@
 #include "pathfind.h"
 #include "town.h"
 #include "sound.h"
+#include "station.h"
 
 void ShowTrainDepotWindow(uint tile);
 
@@ -734,6 +735,11 @@
 
 	if (flags & DC_EXEC) {
 		ModifyTile(tile, MP_MAP5, RAIL_TYPE_CHECKPOINT | dir);
+		if (p1 & 0x100) {
+			// custom graphics
+			_map3_lo[tile] |= 16;
+			_map3_hi[tile] = p1 & 0xff;
+		}
 
 		cp->deleted = 0;
 		cp->xy = tile;
@@ -790,6 +796,8 @@
 
 		if (justremove) {
 			ModifyTile(tile, MP_MAP5, 1<<direction);
+			_map3_lo[tile] &= ~16;
+			_map3_hi[tile] = 0;
 		} else {
 			DoClearSquare(tile);
 			SetSignalsOnBothDir(tile, direction);
@@ -1405,6 +1413,19 @@
 	DetTrackDrawProc_Null,
 };
 
+static void DrawSpecialBuilding(uint32 image, uint32 tracktype_offs,
+                                TileInfo *ti,
+                                byte x, byte y, byte z,
+                                byte xsize, byte ysize, byte zsize)
+{
+	if (image & 0x8000)
+		image |= _drawtile_track_palette;
+	image += tracktype_offs;
+	if (!(_display_opt & DO_TRANS_BUILDINGS)) // show transparent depots
+		image = (image & 0x3FFF) | 0x3224000;
+	AddSortableSpriteToDraw(image, ti->x + x, ti->y + y, xsize, ysize, zsize, ti->z + z);
+}
+
 static void DrawTile_Track(TileInfo *ti)
 {
 	uint32 tracktype_offs, image;
@@ -1521,6 +1542,28 @@
 
 		if (ti->tileh != 0) { DrawFoundation(ti, ti->tileh); }
 
+		if (!IS_RAIL_DEPOT(m5) && IS_RAIL_CHECKPOINT(m5) && _map3_lo[ti->tile]&16) {
+			// look for customization
+			DrawTileSprites *cust = GetCustomStation('WAYP', _map3_hi[ti->tile]);
+
+			if (cust) {
+				DrawTileSeqStruct const *seq;
+
+				cust = &cust[2 + (m5 & 0x1)]; // emulate station tile - open with building
+
+				image = cust->ground_sprite;
+				if (image & 0x8000) image = (image & 0x7FFF) + tracktype_offs;
+				DrawGroundSprite(image);
+
+				foreach_draw_tile_seq(seq, cust->seq) {
+					DrawSpecialBuilding(seq->image|0x8000, 0, ti,
+					                    seq->delta_x, seq->delta_y, seq->delta_z,
+					                    seq->width, seq->height, seq->unk);
+				}
+				return;
+			}
+		}
+
 		s = _track_depot_layout_table[type];
 
 		image = *(const uint16*)s;
@@ -1541,13 +1584,9 @@
 		drss = (const DrawTrackSeqStruct*)(s + sizeof(uint16));
 
 		while ((image=drss->image) != 0) {
-			if (image & 0x8000)
-				image |= _drawtile_track_palette;
-			image += (type<4)?tracktype_offs:0;
-			if (!(_display_opt & DO_TRANS_BUILDINGS)) // show transparent depots
-				image = (image & 0x3FFF) | 0x3224000;
-			AddSortableSpriteToDraw(image, ti->x | drss->subcoord_x,
-				ti->y | drss->subcoord_y, drss->width, drss->height, 0x17, ti->z);
+			DrawSpecialBuilding(image, type < 4 ? tracktype_offs : 0, ti,
+			                    drss->subcoord_x, drss->subcoord_y, 0,
+			                    drss->width, drss->height, 0x17);
 			drss++;
 		}
 	}
--- a/rail_gui.c	Sun Nov 14 08:15:50 2004 +0000
+++ b/rail_gui.c	Sun Nov 14 09:07:15 2004 +0000
@@ -114,7 +114,12 @@
 static void PlaceRail_Checkpoint(uint tile)
 {
 	if (!_remove_button_clicked) {
-		DoCommandP(tile, 0, 0, CcPlaySound1E, CMD_BUILD_TRAIN_CHECKPOINT | CMD_MSG(STR_CANT_BUILD_TRAIN_CHECKPOINT));
+		/* TODO: We need a graphics selector. In the meantime we use the first
+		 * custom station ID which works ok with newstats.grf (if you add it
+		 * to openttd.cfg you want custom checkpoints) and if you don't have
+		 * any custom station graphics it will fall back to the railstation
+		 * sprites anyway. --pasky */
+		DoCommandP(tile, 0x100, 0, CcPlaySound1E, CMD_BUILD_TRAIN_CHECKPOINT | CMD_MSG(STR_CANT_BUILD_TRAIN_CHECKPOINT));
 	} else {
 		DoCommandP(tile, 0, 0, CcPlaySound1E, CMD_REMOVE_TRAIN_CHECKPOINT | CMD_MSG(STR_CANT_REMOVE_TRAIN_CHECKPOINT));
 	}
--- a/station.h	Sun Nov 14 08:15:50 2004 +0000
+++ b/station.h	Sun Nov 14 09:07:15 2004 +0000
@@ -107,4 +107,8 @@
 
 #define foreach_draw_tile_seq(idx, list) for (idx = list; ((byte) idx->delta_x) != 0x80; idx++)
 
+void SetCustomStation(uint32 classid, byte stid, DrawTileSprites *data, byte tiles);
+DrawTileSprites *GetCustomStation(uint32 classid, byte stid);
+int GetCustomStationsCount(uint32 classid);
+
 #endif /* STATION_H */
--- a/station_cmd.c	Sun Nov 14 08:15:50 2004 +0000
+++ b/station_cmd.c	Sun Nov 14 09:07:15 2004 +0000
@@ -957,6 +957,38 @@
 }
 
 
+/* TODO: Multiple classes! */
+/* FIXME: Also, we should actually allocate the station id (but
+ * SetCustomStation() needs to be able to override an existing custom station
+ * as well) on our own. This would also prevent possible weirdness if some GRF
+ * file used non-contignuous station ids. --pasky */
+
+static int _waypoint_highest_id = -1;
+static DrawTileSprites _waypoint_data[256][8];
+
+void SetCustomStation(uint32 classid, byte stid, DrawTileSprites *data, byte tiles)
+{
+	assert(classid == 'WAYP');
+	if (stid > _waypoint_highest_id)
+		_waypoint_highest_id = stid;
+	memcpy(_waypoint_data[stid], data, sizeof(DrawTileSprites) * tiles);
+}
+
+DrawTileSprites *GetCustomStation(uint32 classid, byte stid)
+{
+	assert(classid == 'WAYP');
+	if (stid > _waypoint_highest_id || !_waypoint_data || !_waypoint_data[stid])
+		return NULL;
+	return _waypoint_data[stid];
+}
+
+int GetCustomStationsCount(uint32 classid)
+{
+	assert(classid == 'WAYP');
+	return _waypoint_highest_id + 1;
+}
+
+
 static int32 RemoveRailroadStation(Station *st, TileIndex tile, uint32 flags)
 {
 	int w,h;