waypoint.c
author bjarni
Thu, 05 Oct 2006 12:59:28 +0000
changeset 4739 e626ef1b522e
parent 4527 fa30d19685c2
child 4984 f717899efa25
permissions -rw-r--r--
(svn r6651) -Coding feature: added the windowevent WE_INVALIDATE_DATA
This gives the ability to invalidate some window data and recalculate as needed instead of doing it for each WE_PAINT

This event is called right away when using InvalidateWindowData(), so it may be a good idea to set a bool or similar in the window
or similar and then act on that bool in WE_PAINT instead of doing a lot of stuff in WE_INVALIDATE_DATA as it might be called more than once before WE_PAINT is called

InvalidateWindowData() will not automatically repaint the window, so if you want to repaint it as well, you need to mark it dirty as well.

Made the depot windows use WE_INVALIDATE_DATA to set when to generate the engine and wagon lists instead of at each redraw
It makes no sense to regenerate the list when say using the scrollbar if we know that no vehicle have entered or left the list

NOTE: currently there is a piece of code to generate the list when it's not needed and compare it to the stored list and assert if they mismatch
This check is somewhat slow and kills the whole idea of WE_INVALIDATE_DATA, so it's a short lived one to verify that InvalidateWindowData() is used everywhere where it's needed
/* $Id$ */

#include "stdafx.h"
#include "openttd.h"

#include "command.h"
#include "functions.h"
#include "gfx.h"
#include "map.h"
#include "order.h"
#include "rail_map.h"
#include "saveload.h"
#include "station.h"
#include "tile.h"
#include "town.h"
#include "waypoint.h"
#include "variables.h"
#include "table/strings.h"
#include "vehicle.h"
#include "yapf/yapf.h"
#include "date.h"

enum {
	/* Max waypoints: 64000 (8 * 8000) */
	WAYPOINT_POOL_BLOCK_SIZE_BITS = 3,       /* In bits, so (1 << 3) == 8 */
	WAYPOINT_POOL_MAX_BLOCKS      = 8000,

	MAX_WAYPOINTS_PER_TOWN        = 64,
};

/**
 * Called if a new block is added to the waypoint-pool
 */
static void WaypointPoolNewBlock(uint start_item)
{
	Waypoint *wp;

	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 * TODO - This is just a temporary stage, this will be removed. */
	for (wp = GetWaypoint(start_item); wp != NULL; wp = (wp->index + 1 < GetWaypointPoolSize()) ? GetWaypoint(wp->index + 1) : NULL) wp->index = start_item++;
}

/* Initialize the town-pool */
MemoryPool _waypoint_pool = { "Waypoints", WAYPOINT_POOL_MAX_BLOCKS, WAYPOINT_POOL_BLOCK_SIZE_BITS, sizeof(Waypoint), &WaypointPoolNewBlock, NULL, 0, 0, NULL };

/* Create a new waypoint */
static Waypoint* AllocateWaypoint(void)
{
	Waypoint *wp;

	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 * TODO - This is just a temporary stage, this will be removed. */
	for (wp = GetWaypoint(0); wp != NULL; wp = (wp->index + 1 < GetWaypointPoolSize()) ? GetWaypoint(wp->index + 1) : NULL) {
		if (!IsValidWaypoint(wp)) {
			uint index = wp->index;

			memset(wp, 0, sizeof(*wp));
			wp->index = index;

			return wp;
		}
	}

	/* Check if we can add a block to the pool */
	if (AddBlockToPool(&_waypoint_pool)) return AllocateWaypoint();

	return NULL;
}

/* Update the sign for the waypoint */
static void UpdateWaypointSign(Waypoint* wp)
{
	Point pt = RemapCoords2(TileX(wp->xy) * TILE_SIZE, TileY(wp->xy) * TILE_SIZE);
	SetDParam(0, wp->index);
	UpdateViewportSignPos(&wp->sign, pt.x, pt.y - 0x20, STR_WAYPOINT_VIEWPORT);
}

/* Redraw the sign of a waypoint */
static void RedrawWaypointSign(const Waypoint* wp)
{
	MarkAllViewportsDirty(
		wp->sign.left - 6,
		wp->sign.top,
		wp->sign.left + (wp->sign.width_1 << 2) + 12,
		wp->sign.top + 48);
}

/* Update all signs */
void UpdateAllWaypointSigns(void)
{
	Waypoint *wp;

	FOR_ALL_WAYPOINTS(wp) {
		UpdateWaypointSign(wp);
	}
}

/* Internal handler to delete a waypoint */
void DestroyWaypoint(Waypoint *wp)
{
	RemoveOrderFromAllVehicles(OT_GOTO_WAYPOINT, wp->index);

	if (wp->string != STR_NULL) DeleteName(wp->string);

	RedrawWaypointSign(wp);
}

/* Set the default name for a waypoint */
static void MakeDefaultWaypointName(Waypoint* wp)
{
	Waypoint *local_wp;
	bool used_waypoint[MAX_WAYPOINTS_PER_TOWN];
	int i;

	wp->town_index = ClosestTownFromTile(wp->xy, (uint)-1)->index;

	memset(used_waypoint, 0, sizeof(used_waypoint));

	/* Find an unused waypoint number belonging to this town */
	FOR_ALL_WAYPOINTS(local_wp) {
		if (wp == local_wp) continue;

		if (local_wp->xy && local_wp->string == STR_NULL && local_wp->town_index == wp->town_index)
			used_waypoint[local_wp->town_cn] = true;
	}

	/* Find an empty spot */
	for (i = 0; used_waypoint[i] && i < MAX_WAYPOINTS_PER_TOWN; i++) {}

	wp->string = STR_NULL;
	wp->town_cn = i;
}

/* Find a deleted waypoint close to a tile. */
static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile)
{
	Waypoint *wp, *best = NULL;
	uint thres = 8;

	FOR_ALL_WAYPOINTS(wp) {
		if (wp->deleted) {
			uint cur_dist = DistanceManhattan(tile, wp->xy);

			if (cur_dist < thres) {
				thres = cur_dist;
				best = wp;
			}
		}
	}

	return best;
}

/**
 * Update waypoint graphics id against saved GRFID/localidx.
 * This is to ensure the chosen graphics are correct if GRF files are changed.
 */
void UpdateAllWaypointCustomGraphics(void)
{
	Waypoint *wp;

	FOR_ALL_WAYPOINTS(wp) {
		uint i;

		if (wp->grfid == 0) continue;

		for (i = 0; i < GetNumCustomStations(STAT_CLASS_WAYP); i++) {
			const StationSpec *statspec = GetCustomStationSpec(STAT_CLASS_WAYP, i);
			if (statspec != NULL && statspec->grfid == wp->grfid && statspec->localidx == wp->localidx) {
				wp->stat_id = i;
				break;
			}
		}
	}
}

/** Convert existing rail to waypoint. Eg build a waypoint station over
 * piece of rail
 * @param tile tile where waypoint will be built
 * @param p1 graphics for waypoint type, 0 indicates standard graphics
 * @param p2 unused
 *
 * @todo When checking for the tile slope,
 * distingush between "Flat land required" and "land sloped in wrong direction"
 */
int32 CmdBuildTrainWaypoint(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
{
	Waypoint *wp;
	Slope tileh;
	Axis axis;

	SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);

	/* if custom gfx are used, make sure it is within bounds */
	if (p1 >= GetNumCustomStations(STAT_CLASS_WAYP)) return CMD_ERROR;

	if (!IsTileType(tile, MP_RAILWAY) ||
			GetRailTileType(tile) != RAIL_TILE_NORMAL || (
				(axis = AXIS_X, GetTrackBits(tile) != TRACK_BIT_X) &&
				(axis = AXIS_Y, GetTrackBits(tile) != TRACK_BIT_Y)
			)) {
		return_cmd_error(STR_1005_NO_SUITABLE_RAILROAD_TRACK);
	}

	if (!CheckTileOwnership(tile)) return CMD_ERROR;
	if (!EnsureNoVehicle(tile)) return CMD_ERROR;

	tileh = GetTileSlope(tile, NULL);
	if (tileh != SLOPE_FLAT &&
			(!_patches.build_on_slopes || IsSteepSlope(tileh) || !(tileh & (0x3 << axis)) || !(tileh & ~(0x3 << axis)))) {
		return_cmd_error(STR_0007_FLAT_LAND_REQUIRED);
	}

	/* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
	wp = FindDeletedWaypointCloseTo(tile);
	if (wp == NULL) {
		wp = AllocateWaypoint();
		if (wp == NULL) return CMD_ERROR;

		wp->town_index = 0;
		wp->string = STR_NULL;
		wp->town_cn = 0;
	}

	if (flags & DC_EXEC) {
		const StationSpec* statspec;

		MakeRailWaypoint(tile, GetTileOwner(tile), axis, GetRailType(tile), wp->index);
		MarkTileDirtyByTile(tile);

		statspec = GetCustomStationSpec(STAT_CLASS_WAYP, p1);

		if (statspec != NULL) {
			wp->stat_id = p1;
			wp->grfid = statspec->grfid;
			wp->localidx = statspec->localidx;
		} else {
			// Specified custom graphics do not exist, so use default.
			wp->stat_id = 0;
			wp->grfid = 0;
			wp->localidx = 0;
		}

		wp->deleted = 0;
		wp->xy = tile;
		wp->build_date = _date;

		if (wp->town_index == 0) MakeDefaultWaypointName(wp);

		UpdateWaypointSign(wp);
		RedrawWaypointSign(wp);
	}

	return _price.build_train_depot;
}

/* Daily loop for waypoints */
void WaypointsDailyLoop(void)
{
	Waypoint *wp;

	/* Check if we need to delete a waypoint */
	FOR_ALL_WAYPOINTS(wp) {
		if (wp->deleted != 0 && --wp->deleted == 0) DeleteWaypoint(wp);
	}
}

/* Remove a waypoint */
int32 RemoveTrainWaypoint(TileIndex tile, uint32 flags, bool justremove)
{
	Waypoint *wp;

	/* Make sure it's a waypoint */
	if (!IsTileType(tile, MP_RAILWAY) ||
			!IsRailWaypoint(tile) ||
			(!CheckTileOwnership(tile) && _current_player != OWNER_WATER) ||
			!EnsureNoVehicle(tile)) {
		return CMD_ERROR;
	}

	if (flags & DC_EXEC) {
		wp = GetWaypointByTile(tile);

		wp->deleted = 30; // let it live for this many days before we do the actual deletion.
		RedrawWaypointSign(wp);

		if (justremove) {
			MakeRailNormal(tile, GetTileOwner(tile), GetRailWaypointBits(tile), GetRailType(tile));
			MarkTileDirtyByTile(tile);
		} else {
			DoClearSquare(tile);
			SetSignalsOnBothDir(tile, GetRailWaypointTrack(tile));
			YapfNotifyTrackLayoutChange(tile, GetRailWaypointTrack(tile));
		}
	}

	return _price.remove_train_depot;
}

/** Delete a waypoint
 * @param tile tile where waypoint is to be deleted
 * @param p1 unused
 * @param p2 unused
 */
int32 CmdRemoveTrainWaypoint(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
{
	SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
	return RemoveTrainWaypoint(tile, flags, true);
}

/** Rename a waypoint.
 * @param tile unused
 * @param p1 id of waypoint
 * @param p2 unused
 */
int32 CmdRenameWaypoint(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
{
	Waypoint *wp;

	if (!IsValidWaypointID(p1)) return CMD_ERROR;

	if (_cmd_text[0] != '\0') {
		StringID str = AllocateNameUnique(_cmd_text, 0);

		if (str == 0) return CMD_ERROR;

		if (flags & DC_EXEC) {
			wp = GetWaypoint(p1);
			if (wp->string != STR_NULL) DeleteName(wp->string);

			wp->string = str;
			wp->town_cn = 0;

			UpdateWaypointSign(wp);
			MarkWholeScreenDirty();
		} else {
			DeleteName(str);
		}
	} else {
		if (flags & DC_EXEC) {
			wp = GetWaypoint(p1);
			if (wp->string != STR_NULL) DeleteName(wp->string);

			MakeDefaultWaypointName(wp);
			UpdateWaypointSign(wp);
			MarkWholeScreenDirty();
		}
	}
	return 0;
}

/* This hacks together some dummy one-shot Station structure for a waypoint. */
Station *ComposeWaypointStation(TileIndex tile)
{
	Waypoint *wp = GetWaypointByTile(tile);
	static Station stat;

	stat.train_tile = stat.xy = wp->xy;
	stat.town = GetTown(wp->town_index);
	stat.string_id = wp->string == STR_NULL ? /* FIXME? */ 0 : wp->string;
	stat.build_date = wp->build_date;

	return &stat;
}

/* Draw a waypoint */
void DrawWaypointSprite(int x, int y, int stat_id, RailType railtype)
{
	x += 33;
	y += 17;

	if (!DrawStationTile(x, y, railtype, AXIS_X, STAT_CLASS_WAYP, stat_id)) {
		DrawDefaultWaypointSprite(x, y, railtype);
	}
}

/* Fix savegames which stored waypoints in their old format */
void FixOldWaypoints(void)
{
	Waypoint *wp;

	/* Convert the old 'town_or_string', to 'string' / 'town' / 'town_cn' */
	FOR_ALL_WAYPOINTS(wp) {
		wp->town_index = ClosestTownFromTile(wp->xy, (uint)-1)->index;
		wp->town_cn = 0;
		if (wp->string & 0xC000) {
			wp->town_cn = wp->string & 0x3F;
			wp->string = STR_NULL;
		}
	}
}

void InitializeWaypoints(void)
{
	CleanPool(&_waypoint_pool);
	AddBlockToPool(&_waypoint_pool);
}

static const SaveLoad _waypoint_desc[] = {
	SLE_CONDVAR(Waypoint, xy,         SLE_FILE_U16 | SLE_VAR_U32,  0, 5),
	SLE_CONDVAR(Waypoint, xy,         SLE_UINT32,                  6, SL_MAX_VERSION),
	SLE_CONDVAR(Waypoint, town_index, SLE_UINT16,                 12, SL_MAX_VERSION),
	SLE_CONDVAR(Waypoint, town_cn,    SLE_UINT8,                  12, SL_MAX_VERSION),
	    SLE_VAR(Waypoint, string,     SLE_UINT16),
	    SLE_VAR(Waypoint, deleted,    SLE_UINT8),

	SLE_CONDVAR(Waypoint, build_date, SLE_FILE_U16 | SLE_VAR_I32,  3, 30),
	SLE_CONDVAR(Waypoint, build_date, SLE_INT32,                  31, SL_MAX_VERSION),
	SLE_CONDVAR(Waypoint, localidx,   SLE_UINT8,                   3, SL_MAX_VERSION),
	SLE_CONDVAR(Waypoint, grfid,      SLE_UINT32,                 17, SL_MAX_VERSION),

	SLE_END()
};

static void Save_WAYP(void)
{
	Waypoint *wp;

	FOR_ALL_WAYPOINTS(wp) {
		SlSetArrayIndex(wp->index);
		SlObject(wp, _waypoint_desc);
	}
}

static void Load_WAYP(void)
{
	int index;

	while ((index = SlIterateArray()) != -1) {
		Waypoint *wp;

		if (!AddBlockIfNeeded(&_waypoint_pool, index))
			error("Waypoints: failed loading savegame: too many waypoints");

		wp = GetWaypoint(index);
		SlObject(wp, _waypoint_desc);
	}
}

const ChunkHandler _waypoint_chunk_handlers[] = {
	{ 'CHKP', Save_WAYP, Load_WAYP, CH_ARRAY | CH_LAST},
};