diff --git a/src/economy.cpp b/src/economy.cpp index b12a912e16..9c55781d2d 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1718,6 +1718,16 @@ static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) } } + /* Calculate the loading indicator fill percent and display */ + if (_patches.loading_indicators && _game_mode != GM_MENU && v->owner == _local_player) { + int percent = CalcPercentVehicleFilled(v); + if (v->fill_percent_te_id == INVALID_TE_ID) { + v->fill_percent_te_id = ShowFillingPercent(v->x_pos, v->y_pos, v->z_pos + 20, percent); + } else { + UpdateFillingPercent(v->fill_percent_te_id, percent); + } + } + v->load_unload_time_rem = unloading_time; if (completely_empty) { diff --git a/src/functions.h b/src/functions.h index ba6d737e38..7c032b70c3 100644 --- a/src/functions.h +++ b/src/functions.h @@ -79,16 +79,6 @@ uint32 InteractiveRandom(); // Used for random sequences that are not the same o uint InteractiveRandomRange(uint max); /* texteff.cpp */ -void MoveAllTextEffects(); -void AddTextEffect(StringID msg, int x, int y, uint16 duration); -void InitTextEffects(); -void DrawTextEffects(DrawPixelInfo *dpi); - -void InitTextMessage(); -void DrawTextMessage(); -void CDECL AddTextMessage(uint16 color, uint8 duration, const char *message, ...); -void UndrawTextMessage(); - bool AddAnimatedTile(TileIndex tile); void DeleteAnimatedTile(TileIndex tile); void AnimateAnimatedTiles(); diff --git a/src/gfx.cpp b/src/gfx.cpp index 4cf2820cfb..7d2c9b2734 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -19,6 +19,7 @@ #include "genworld.h" #include "debug.h" #include "zoom.hpp" +#include "texteff.hpp" #include "blitter/factory.hpp" #ifdef _DEBUG diff --git a/src/lang/english.txt b/src/lang/english.txt index dd9a1ba846..889d8b2634 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1103,6 +1103,7 @@ STR_CONFIG_PATCHES_SCROLLWHEEL_OFF :Off STR_CONFIG_PATCHES_SCROLLWHEEL_MULTIPLIER :{LTBLUE}Map scrollwheel speed: {ORANGE}{STRING1} STR_CONFIG_PATCHES_PAUSE_ON_NEW_GAME :{LTBLUE}Automatically pause when starting a new game: {ORANGE}{STRING1} STR_CONFIG_PATCHES_ADVANCED_VEHICLE_LISTS :{LTBLUE}Use the advanced vehicle list: {ORANGE}{STRING1} +STR_CONFIG_PATCHES_LOADING_INDICATORS :{LTBLUE}Use loading indicators: {ORANGE}{STRING1} STR_CONFIG_PATCHES_TIMETABLE_ALLOW :{LTBLUE}Enable timetabling for vehicles: {ORANGE}{STRING1} STR_CONFIG_PATCHES_TIMETABLE_IN_TICKS :{LTBLUE}Show timetable in ticks rather than days: {ORANGE}{STRING1} @@ -3289,6 +3290,10 @@ STR_TRANSPARENT_INDUSTRIES_DESC :{BLACK}Toggle t STR_TRANSPARENT_BUILDINGS_DESC :{BLACK}Toggle transparency for buildables like stations, depots, waypoints and catenary STR_TRANSPARENT_BRIDGES_DESC :{BLACK}Toggle transparency for bridges STR_TRANSPARENT_STRUCTURES_DESC :{BLACK}Toggle transparency for structures like lighthouses and antennas, maybe in future for eyecandy +STR_TRANSPARENT_LOADING_DESC :{BLACK}Toggle transparency for loading indicators + +STR_PERCENT_FULL_SMALL :{TINYFONT}{WHITE}{NUM}% +STR_PERCENT_FULL :{WHITE}{NUM}% ##### Mass Order STR_GROUP_NAME_FORMAT :Group {COMMA} diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 5d887cc177..1a5b9bbd8c 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -633,7 +633,7 @@ void ShowCostOrIncomeAnimation(int x, int y, int z, Money cost) msg = STR_0803_INCOME; } SetDParamMoney(0, cost); - AddTextEffect(msg, pt.x, pt.y, 0x250); + AddTextEffect(msg, pt.x, pt.y, 0x250, TE_RISING); } void ShowFeederIncomeAnimation(int x, int y, int z, Money cost) @@ -641,7 +641,26 @@ void ShowFeederIncomeAnimation(int x, int y, int z, Money cost) Point pt = RemapCoords(x,y,z); SetDParamMoney(0, cost); - AddTextEffect(STR_FEEDER, pt.x, pt.y, 0x250); + AddTextEffect(STR_FEEDER, pt.x, pt.y, 0x250, TE_RISING); +} + +TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent) +{ + Point pt = RemapCoords(x, y, z); + + SetDParam(0, percent); + return AddTextEffect(STR_PERCENT_FULL, pt.x, pt.y, 0xFFFF, TE_STATIC); +} + +void UpdateFillingPercent(TextEffectID te_id, uint8 percent) +{ + SetDParam(0, percent); + UpdateTextEffect(te_id, STR_PERCENT_FULL); +} + +void HideFillingPercent(TextEffectID te_id) +{ + if (te_id != INVALID_TE_ID) RemoveTextEffect(te_id); } static const Widget _tooltips_widgets[] = { diff --git a/src/openttd.h b/src/openttd.h index a768da3bd4..6f2b1556c4 100644 --- a/src/openttd.h +++ b/src/openttd.h @@ -192,6 +192,7 @@ enum { TO_BUILDINGS, TO_BRIDGES, TO_STRUCTURES, + TO_LOADING, }; /* Landscape types */ diff --git a/src/settings.cpp b/src/settings.cpp index f0454ad736..7db552a723 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1350,6 +1350,7 @@ const SettingDesc _patch_settings[] = { SDT_BOOL(Patches, pause_on_newgame, S, 0, false, STR_CONFIG_PATCHES_PAUSE_ON_NEW_GAME, NULL), SDT_BOOL(Patches, advanced_vehicle_list, S, 0, true, STR_CONFIG_PATCHES_ADVANCED_VEHICLE_LISTS, NULL), SDT_BOOL(Patches, timetable_in_ticks, S, 0, false, STR_CONFIG_PATCHES_TIMETABLE_IN_TICKS, NULL), + SDT_BOOL(Patches, loading_indicators, S, 0, true, STR_CONFIG_PATCHES_LOADING_INDICATORS, RedrawScreen), /***************************************************************************/ /* Construction section of the GUI-configure patches window */ diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 000d50f2c9..8148f61dff 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -652,6 +652,7 @@ static const char *_patches_ui[] = { "scrollwheel_multiplier", "pause_on_newgame", "advanced_vehicle_list", + "loading_indicators", "timetable_in_ticks", }; diff --git a/src/texteff.cpp b/src/texteff.cpp index 0a35973e1a..ca3bdf4cff 100644 --- a/src/texteff.cpp +++ b/src/texteff.cpp @@ -19,10 +19,11 @@ #include "blitter/factory.hpp" #include /* va_list */ #include "date.h" +#include "texteff.hpp" enum { MAX_TEXTMESSAGE_LENGTH = 200, - MAX_TEXT_MESSAGES = 30, + INIT_NUM_TEXT_MESSAGES = 20, MAX_CHAT_MESSAGES = 10, MAX_ANIMATED_TILES = 256, }; @@ -36,6 +37,7 @@ struct TextEffect { uint16 duration; uint32 params_1; uint32 params_2; + TextEffectMode mode; }; @@ -45,12 +47,13 @@ struct TextMessage { Date end_date; }; -static TextEffect _text_effect_list[MAX_TEXT_MESSAGES]; +static TextEffect *_text_effect_list = NULL; static TextMessage _textmsg_list[MAX_CHAT_MESSAGES]; TileIndex _animated_tile_list[MAX_ANIMATED_TILES]; static bool _textmessage_dirty = false; static bool _textmessage_visible = false; +static uint16 _num_text_effects = INIT_NUM_TEXT_MESSAGES; /* The chatbox grows from the bottom so the coordinates are pixels from * the left and pixels from the bottom. The height is the maximum height */ @@ -261,24 +264,38 @@ static void MarkTextEffectAreaDirty(TextEffect *te) ); } -void AddTextEffect(StringID msg, int x, int y, uint16 duration) +TextEffectID AddTextEffect(StringID msg, int x, int y, uint16 duration, TextEffectMode mode) { TextEffect *te; int w; char buffer[100]; + TextEffectID i; - if (_game_mode == GM_MENU) return; + if (_game_mode == GM_MENU) return INVALID_TE_ID; - for (te = _text_effect_list; te->string_id != INVALID_STRING_ID; ) { - if (++te == endof(_text_effect_list)) return; + /* Look for a free spot in the text effect array */ + for (i = 0; i < _num_text_effects; i++) { + if (_text_effect_list[i].string_id == INVALID_STRING_ID) break; } + /* If there is none found, we grow the array */ + if (i == _num_text_effects) { + _num_text_effects += 25; + _text_effect_list = (TextEffect*) realloc(_text_effect_list, _num_text_effects * sizeof(TextEffect)); + for (; i < _num_text_effects; i++) _text_effect_list[i].string_id = INVALID_STRING_ID; + i = _num_text_effects - 1; + } + + te = &_text_effect_list[i]; + + /* Start defining this object */ te->string_id = msg; te->duration = duration; te->y = y - 5; te->bottom = y + 5; te->params_1 = GetDParam(0); te->params_2 = GetDParam(4); + te->mode = mode; GetString(buffer, msg, lastof(buffer)); w = GetStringBoundingBox(buffer).width; @@ -286,10 +303,38 @@ void AddTextEffect(StringID msg, int x, int y, uint16 duration) te->x = x - (w >> 1); te->right = x + (w >> 1) - 1; MarkTextEffectAreaDirty(te); + + return i; +} + +void UpdateTextEffect(TextEffectID te_id, StringID msg) +{ + assert(te_id < _num_text_effects); + TextEffect *te; + + /* Update details */ + te = &_text_effect_list[te_id]; + te->string_id = msg; + te->params_1 = GetDParam(0); + te->params_2 = GetDParam(4); + + MarkTextEffectAreaDirty(te); +} + +void RemoveTextEffect(TextEffectID te_id) +{ + assert(te_id < _num_text_effects); + TextEffect *te; + + te = &_text_effect_list[te_id]; + MarkTextEffectAreaDirty(te); + te->string_id = INVALID_STRING_ID; } static void MoveTextEffect(TextEffect *te) { + /* Never expire for duration of 0xFFFF */ + if (te->duration == 0xFFFF) return; if (te->duration < 8) { te->string_id = INVALID_STRING_ID; } else { @@ -302,47 +347,48 @@ static void MoveTextEffect(TextEffect *te) void MoveAllTextEffects() { - TextEffect *te; - - for (te = _text_effect_list; te != endof(_text_effect_list); te++) { - if (te->string_id != INVALID_STRING_ID) MoveTextEffect(te); + for (TextEffectID i = 0; i < _num_text_effects; i++) { + TextEffect *te = &_text_effect_list[i]; + if (te->string_id != INVALID_STRING_ID && te->mode == TE_RISING) MoveTextEffect(te); } } void InitTextEffects() { - TextEffect *te; + if (_text_effect_list == NULL) _text_effect_list = MallocT(_num_text_effects); - for (te = _text_effect_list; te != endof(_text_effect_list); te++) { - te->string_id = INVALID_STRING_ID; - } + for (TextEffectID i = 0; i < _num_text_effects; i++) _text_effect_list[i].string_id = INVALID_STRING_ID; } void DrawTextEffects(DrawPixelInfo *dpi) { - const TextEffect* te; - switch (dpi->zoom) { case ZOOM_LVL_NORMAL: - for (te = _text_effect_list; te != endof(_text_effect_list); te++) { + for (TextEffectID i = 0; i < _num_text_effects; i++) { + TextEffect *te = &_text_effect_list[i]; if (te->string_id != INVALID_STRING_ID && dpi->left <= te->right && dpi->top <= te->bottom && dpi->left + dpi->width > te->x && dpi->top + dpi->height > te->y) { - AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2); + if (te->mode == TE_RISING || (_patches.loading_indicators && !HASBIT(_transparent_opt, TO_LOADING))) { + AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2); + } } } break; case ZOOM_LVL_OUT_2X: - for (te = _text_effect_list; te != endof(_text_effect_list); te++) { + for (TextEffectID i = 0; i < _num_text_effects; i++) { + TextEffect *te = &_text_effect_list[i]; if (te->string_id != INVALID_STRING_ID && dpi->left <= te->right * 2 - te->x && dpi->top <= te->bottom * 2 - te->y && dpi->left + dpi->width > te->x && dpi->top + dpi->height > te->y) { - AddStringToDraw(te->x, te->y, (StringID)(te->string_id-1), te->params_1, te->params_2); + if (te->mode == TE_RISING || (_patches.loading_indicators && !HASBIT(_transparent_opt, TO_LOADING))) { + AddStringToDraw(te->x, te->y, (StringID)(te->string_id - 1), te->params_1, te->params_2); + } } } break; diff --git a/src/texteff.hpp b/src/texteff.hpp new file mode 100644 index 0000000000..96971d8ef5 --- /dev/null +++ b/src/texteff.hpp @@ -0,0 +1,35 @@ +/* $Id$ */ + +#ifndef TEXTEFF_HPP +#define TEXTEFF_HPP + +/** + * Text effect modes. + */ +enum TextEffectMode { + TE_RISING, ///< Make the text effect slowly go upwards + TE_STATIC, ///< Keep the text effect static + + INVALID_TE_ID = 0xFFFF, +}; + +typedef uint16 TextEffectID; + +void MoveAllTextEffects(); +TextEffectID AddTextEffect(StringID msg, int x, int y, uint16 duration, TextEffectMode mode); +void InitTextEffects(); +void DrawTextEffects(DrawPixelInfo *dpi); +void UpdateTextEffect(TextEffectID effect_id, StringID msg); +void RemoveTextEffect(TextEffectID effect_id); + +void InitTextMessage(); +void DrawTextMessage(); +void CDECL AddTextMessage(uint16 color, uint8 duration, const char *message, ...); +void UndrawTextMessage(); + +/* misc_gui.cpp */ +TextEffectID ShowFillingPercent(int x, int y, int z, uint8 percent); +void UpdateFillingPercent(TextEffectID te_id, uint8 percent); +void HideFillingPercent(TextEffectID te_id); + +#endif /* TEXTEFF_HPP */ diff --git a/src/transparency_gui.cpp b/src/transparency_gui.cpp index 889e117f17..4fcf37f447 100644 --- a/src/transparency_gui.cpp +++ b/src/transparency_gui.cpp @@ -23,6 +23,7 @@ enum TransparencyToolbarWidgets{ TTW_WIDGET_BUILDINGS, ///< Make player buildings and structures transparent TTW_WIDGET_BRIDGES, ///< Make bridges transparent TTW_WIDGET_STRUCTURES, ///< Make unmovable structures transparent + TTW_WIDGET_LOADING, ///< Make loading indicators transperent TTW_WIDGET_END, ///< End of toggle buttons }; @@ -59,11 +60,11 @@ static void TransparencyToolbWndProc(Window *w, WindowEvent *e) static const Widget _transparency_widgets[] = { { WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_NONE, 7, 11, 162, 0, 13, STR_TRANSPARENCY_TOOLB, STR_018C_WINDOW_TITLE_DRAG_THIS}, -{WWT_STICKYBOX, RESIZE_NONE, 7, 163, 174, 0, 13, STR_NULL, STR_STICKY_BUTTON}, +{ WWT_CAPTION, RESIZE_NONE, 7, 11, 184, 0, 13, STR_TRANSPARENCY_TOOLB, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{WWT_STICKYBOX, RESIZE_NONE, 7, 185, 196, 0, 13, STR_NULL, STR_STICKY_BUTTON}, /* transparency widgets: - * transparent signs, trees, houses, industries, player's buildings, bridges and unmovable structures */ + * transparent signs, trees, houses, industries, player's buildings, bridges, unmovable structures and loading indicators */ { WWT_IMGBTN, RESIZE_NONE, 7, 0, 21, 14, 35, SPR_IMG_SIGN, STR_TRANSPARENT_SIGNS_DESC}, { WWT_IMGBTN, RESIZE_NONE, 7, 22, 43, 14, 35, SPR_IMG_PLANTTREES, STR_TRANSPARENT_TREES_DESC}, { WWT_IMGBTN, RESIZE_NONE, 7, 44, 65, 14, 35, SPR_IMG_TOWN, STR_TRANSPARENT_HOUSES_DESC}, @@ -71,12 +72,13 @@ static const Widget _transparency_widgets[] = { { WWT_IMGBTN, RESIZE_NONE, 7, 88, 109, 14, 35, SPR_IMG_COMPANY_LIST, STR_TRANSPARENT_BUILDINGS_DESC}, { WWT_IMGBTN, RESIZE_NONE, 7, 110, 152, 14, 35, SPR_IMG_BRIDGE, STR_TRANSPARENT_BRIDGES_DESC}, { WWT_IMGBTN, RESIZE_NONE, 7, 153, 174, 14, 35, SPR_IMG_TRANSMITTER, STR_TRANSPARENT_STRUCTURES_DESC}, +{ WWT_IMGBTN, RESIZE_NONE, 7, 175, 196, 14, 35, SPR_IMG_TRAINLIST, STR_TRANSPARENT_LOADING_DESC}, { WIDGETS_END}, }; static const WindowDesc _transparency_desc = { - WDP_ALIGN_TBR, 58+36, 175, 36, + WDP_ALIGN_TBR, 58+36, 197, 36, WC_TRANSPARENCY_TOOLBAR, WC_NONE, WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON, _transparency_widgets, diff --git a/src/variables.h b/src/variables.h index de6a0b738c..761f5f2a36 100644 --- a/src/variables.h +++ b/src/variables.h @@ -133,6 +133,7 @@ struct Patches { byte liveries; // Options for displaying company liveries, 0=none, 1=self, 2=all bool prefer_teamchat; // Choose the chat message target with , true=all players, false=your team bool advanced_vehicle_list; // Use the "advanced" vehicle list + bool loading_indicators; // Show loading indicators uint8 toolbar_pos; // position of toolbars, 0=left, 1=center, 2=right uint8 window_snap_radius; // Windows snap at each other if closer than this diff --git a/src/vehicle.cpp b/src/vehicle.cpp index c00c3fe4a5..53bdfdf514 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -229,6 +229,7 @@ void AfterLoadVehicles() FOR_ALL_VEHICLES(v) { v->UpdateDeltaXY(v->direction); + v->fill_percent_te_id = INVALID_TE_ID; v->first = NULL; if (v->type == VEH_TRAIN) v->u.rail.first_engine = INVALID_ENGINE; if (v->type == VEH_ROAD) v->u.road.first_engine = INVALID_ENGINE; @@ -296,6 +297,7 @@ static Vehicle *InitializeVehicle(Vehicle *v) v->depot_list = NULL; v->random_bits = 0; v->group_id = DEFAULT_GROUP; + v->fill_percent_te_id = INVALID_TE_ID; return v; } @@ -2263,6 +2265,29 @@ bool IsVehicleInDepot(const Vehicle *v) return false; } +/** + * Calculates how full a vehicle is. + * @param v The Vehicle to check. For trains, use the first engine. + * @return A percentage of how full the Vehicle is. + */ +uint8 CalcPercentVehicleFilled(Vehicle *v) +{ + int count = 0; + int max = 0; + + /* Count up max and used */ + for (; v != NULL; v = v->next) { + count += v->cargo_count; + max += v->cargo_cap; + } + + /* Train without capacity */ + if (max == 0) return 100; + + /* Return the percentage */ + return (count * 100) / max; +} + void VehicleEnterDepot(Vehicle *v) { switch (v->type) { @@ -3107,6 +3132,9 @@ void Vehicle::LeaveStation() current_order.flags = 0; GetStation(this->last_station_visited)->loading_vehicles.remove(this); + HideFillingPercent(this->fill_percent_te_id); + this->fill_percent_te_id = INVALID_TE_ID; + UpdateVehicleTimetable(this, false); } diff --git a/src/vehicle.h b/src/vehicle.h index c40017879f..9ab0bb6deb 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -9,6 +9,7 @@ #include "order.h" #include "rail.h" #include "road.h" +#include "texteff.hpp" /** The returned bits of VehicleEnterTile. */ enum VehicleEnterTileStatus { @@ -245,6 +246,8 @@ struct Vehicle { int8 y_offs; // y offset for vehicle sprite EngineID engine_type; + TextEffectID fill_percent_te_id; // a text-effect id to a loading indicator object + /* for randomized variational spritegroups * bitmask used to resolve them; parts of it get reseeded when triggers * of corresponding spritegroups get matched */ @@ -506,6 +509,7 @@ void *VehicleFromPos(TileIndex tile, void *data, VehicleFromPosProc *proc); void *VehicleFromPosXY(int x, int y, void *data, VehicleFromPosProc *proc); void CallVehicleTicks(); Vehicle *FindVehicleOnTileZ(TileIndex tile, byte z); +uint8 CalcPercentVehicleFilled(Vehicle *v); void InitializeTrains(); byte VehicleRandomBits();