diff --git a/Makefile b/Makefile index 34889a712..d8a7e2ae7 100644 --- a/Makefile +++ b/Makefile @@ -373,6 +373,7 @@ SOURCES += gui/factorylist_stats_t.cc SOURCES += gui/goods_frame_t.cc SOURCES += gui/goods_stats_t.cc SOURCES += gui/ground_info.cc +SOURCES += gui/groundobj_edit.cc SOURCES += gui/gui_frame.cc SOURCES += gui/gui_theme.cc SOURCES += gui/halt_info.cc diff --git a/gui/groundobj_edit.cc b/gui/groundobj_edit.cc new file mode 100644 index 000000000..d0562d750 --- /dev/null +++ b/gui/groundobj_edit.cc @@ -0,0 +1,169 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +/* + * The trees builder + */ + +#include +#include "../player/finance.h" // convert_money + +#include "../simworld.h" +#include "../simtool.h" +#include "../simmenu.h" + +#include "../dataobj/translator.h" + + +#include "../descriptor/image.h" +#include "../descriptor/ground_desc.h" + +#include "../utils/cbuffer_t.h" +#include "../utils/simrandom.h" +#include "../utils/simstring.h" + +#include "groundobj_edit.h" + + +// new tool definition +tool_plant_groundobj_t groundobj_edit_frame_t::groundobj_tool; +cbuffer_t groundobj_edit_frame_t::param_str; + + +static bool compare_groundobj_desc(const groundobj_desc_t* a, const groundobj_desc_t* b) +{ + int diff = strcmp( a->get_name(), b->get_name() ); + return diff < 0; +} +static bool compare_groundobj_desc_name(const groundobj_desc_t* a, const groundobj_desc_t* b) +{ + int diff = strcmp( translator::translate(a->get_name()), translator::translate(b->get_name()) ); + if(diff ==0) { + diff = strcmp( a->get_name(), b->get_name() ); + } + return diff < 0; +} +static bool compare_groundobj_desc_cost(const groundobj_desc_t* a, const groundobj_desc_t* b) +{ + int diff = a->get_price() - b->get_price(); + if(diff ==0) { + diff = strcmp( a->get_name(), b->get_name() ); + } + return diff < 0; +} + + +groundobj_edit_frame_t::groundobj_edit_frame_t(player_t* player_) : + extend_edit_gui_t(translator::translate("groundobj builder"), player_), + groundobj_list(16) +{ + cont_timeline.set_visible(false); + cb_sortedby.new_component(gui_sorting_item_t::BY_COST); + + desc = NULL; + groundobj_tool.set_default_param(NULL); + + fill_list( is_show_trans_name ); + + cont_right.add_component(&groundobj_image); + building_image.set_visible(false); +} + + +// fill the current groundobj_list +void groundobj_edit_frame_t::fill_list( bool translate ) +{ + groundobj_list.clear(); + const uint8 sortedby = get_sortedby(); + FOR(vector_tpl, const i, groundobj_t::get_all_desc()) { + if ( i && (i->get_allowed_climate_bits() & get_climate()) ) { + switch(sortedby) { + case gui_sorting_item_t::BY_NAME_TRANSLATED: groundobj_list.insert_ordered( i, compare_groundobj_desc_name ); break; + case gui_sorting_item_t::BY_COST: groundobj_list.insert_ordered( i, compare_groundobj_desc_cost ); break; + default: groundobj_list.insert_ordered( i, compare_groundobj_desc ); + } + } + } + + // now build scrolled list + scl.clear_elements(); + scl.set_selection(-1); + FOR(vector_tpl, const i, groundobj_list) { + char const* const name = translate ? translator::translate(i->get_name()): i->get_name(); + scl.new_component(name, SYSCOL_TEXT); + if (i == desc) { + scl.set_selection(scl.get_count()-1); + } + } + // always update current selection (since the tool may depend on it) + change_item_info( scl.get_selection() ); +} + + + +void groundobj_edit_frame_t::change_item_info(sint32 entry) +{ + buf.clear(); + if(entry>=0 && entry<(sint32)groundobj_list.get_count()) { + + desc = groundobj_list[entry]; + + buf.append(translator::translate(desc->get_name())); + buf.append("\n\n"); + + // climates + buf.append( translator::translate("allowed climates:\n") ); + uint16 cl = desc->get_allowed_climate_bits(); + if(cl==0) { + buf.append( translator::translate("None") ); + buf.append("\n"); + } + else { + for(uint16 i=0; i<=arctic_climate; i++ ) { + if(cl & (1<get_seasons(); + if( seasons > 1){ + buf.printf( "\n%s\n", translator::translate("Has Snow")); + buf.printf( "%s %i\n", translator::translate("Seasons"), desc->get_seasons()-1 ); + } + if(desc->get_phases() > 2){ + buf.printf( "\n%s\n", translator::translate("Has slope graphics")); + } + if(desc->can_build_trees_here()){ + buf.printf( "\n%s\n", translator::translate("Can be overgrown") ); + } + buf.printf("\n%s ", translator::translate("[go]price")); + buf.append_money( convert_money( desc->get_price() ) ); + buf.append("\n"); + + if (char const* const maker = desc->get_copyright()) { + buf.append("\n"); + buf.printf(translator::translate("Constructed by %s"), maker); + buf.append("\n"); + } + + groundobj_image.set_image(desc->get_image_id( seasons>2 ? 2 : 0, 0 ), true); + + param_str.clear(); + param_str.printf( "%i%i,%s", bt_climates.pressed, bt_timeline.pressed, desc->get_name() ); + groundobj_tool.set_default_param(param_str); + groundobj_tool.cursor = tool_t::general_tool[TOOL_PLANT_GROUNDOBJ]->cursor; + welt->set_tool( &groundobj_tool, player ); + } + else if(welt->get_tool(player->get_player_nr())==&groundobj_tool) { + desc = NULL; + groundobj_image.set_image(IMG_EMPTY, true); + welt->set_tool( tool_t::general_tool[TOOL_QUERY], player ); + } + info_text.recalc_size(); + reset_min_windowsize(); +} diff --git a/gui/groundobj_edit.h b/gui/groundobj_edit.h new file mode 100644 index 000000000..f2c805cc1 --- /dev/null +++ b/gui/groundobj_edit.h @@ -0,0 +1,52 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef GUI_GROUNDOBJ_EDIT_H +#define GUI_GROUNDOBJ_EDIT_H + + +#include "extend_edit.h" +#include "components/gui_image.h" + + +class groundobj_desc_t; +class tool_plant_groundobj_t; + +/* + * The groundobj builder + */ +class groundobj_edit_frame_t : public extend_edit_gui_t +{ +private: + static tool_plant_groundobj_t groundobj_tool; + static cbuffer_t param_str; + + const groundobj_desc_t *desc; + + gui_image_t groundobj_image; + + vector_tplgroundobj_list; + + void fill_list( bool translate ) OVERRIDE; + + void change_item_info( sint32 i ) OVERRIDE; + +public: + groundobj_edit_frame_t(player_t* player_); + + /** + * in top-level windows the name is displayed in titlebar + * @return the non-translated component name + */ + const char* get_name() const { return "groundobj builder"; } + + /** + * Set the window associated helptext + * @return the filename for the helptext, or NULL + */ + const char* get_help_filename() const OVERRIDE { return "groundobj_build.txt"; } +}; + +#endif diff --git a/gui/simwin.h b/gui/simwin.h index a16748828..61b11b160 100644 --- a/gui/simwin.h +++ b/gui/simwin.h @@ -104,6 +104,7 @@ enum magic_numbers { magic_motd, magic_factory_info, // only used to load/save magic_font, + magic_edit_groundobj, // magic numbers with big jumps between them magic_convoi_info, diff --git a/obj/groundobj.cc b/obj/groundobj.cc index bd1c3f5e1..8c0c2bf84 100644 --- a/obj/groundobj.cc +++ b/obj/groundobj.cc @@ -28,6 +28,7 @@ /******************************** static routines for desc management ****************************************************************/ vector_tpl groundobj_t::groundobj_typen(0); +weighted_vector_tpl* groundobj_t::groundobj_list_per_climate = NULL; stringhashtable_tpl groundobj_t::desc_table; @@ -37,6 +38,52 @@ bool compare_groundobj_desc(const groundobj_desc_t* a, const groundobj_desc_t* b return strcmp(a->get_name(), b->get_name())<0; } +// total number of groundobj for a certain climate +int groundobj_t::get_count(climate cl) +{ + return groundobj_list_per_climate[cl].get_count(); +} + +/** + * groundobj planting function - it takes care of checking suitability of area + */ +bool groundobj_t::plant_groundobj_on_coordinate(koord pos, const groundobj_desc_t *desc, const bool check_climate) +{ + // none there + if( desc_table.empty() ) { + return false; + } + grund_t *gr = welt->lookup_kartenboden(pos); + if( gr ) { + if( gr->ist_natur() && (!check_climate || desc->is_allowed_climate( welt->get_climate(pos) )) ) { + if( gr->get_top() > 0 ) { + switch(gr->obj_bei(0)->get_typ()) { + case obj_t::wolke: + case obj_t::air_vehicle: + case obj_t::leitung: + case obj_t::label: + case obj_t::zeiger: + // ok to built here + break; + case obj_t::baum: + if( desc->can_build_trees_here() ) { + break; + } + /* FALLTHROUGH */ + // leave these (and all other empty) + default: + return false; + } + + } + groundobj_t *g = new groundobj_t(gr->get_pos(), desc); //plants the groundobj + gr->obj_add( g ); + return true; //groundobj was planted - currently unused value is not checked + } + } + return false; +} + bool groundobj_t::successfully_loaded() { @@ -53,6 +100,17 @@ bool groundobj_t::successfully_loaded() groundobj_typen.append( NULL ); DBG_MESSAGE("groundobj_t", "No groundobj found - feature disabled"); } + + delete [] groundobj_list_per_climate; + groundobj_list_per_climate = new weighted_vector_tpl[MAX_CLIMATES]; + for( uint32 typ=0; typis_allowed_climate((climate)j) ) { + groundobj_list_per_climate[j].append(typ, groundobj_typen[typ]->get_distribution_weight()); + } + } + } return true; } @@ -69,6 +127,7 @@ bool groundobj_t::register_desc(groundobj_desc_t *desc) } + /** * also checks for distribution values */ @@ -105,7 +164,12 @@ const groundobj_desc_t *groundobj_t::random_groundobj_for_climate(climate_bits c /******************************* end of static ******************************************/ - +uint16 groundobj_t::random_groundobj_for_climate_intern(climate cl) +{ + // now weight their distribution + weighted_vector_tpl const& g = groundobj_list_per_climate[cl]; + return g.empty() ? 0xFFFF : pick_any_weighted(g); +} // recalculates only the seasonal image diff --git a/obj/groundobj.h b/obj/groundobj.h index a921fc0f2..48cb0db95 100644 --- a/obj/groundobj.h +++ b/obj/groundobj.h @@ -11,6 +11,7 @@ #include "../tpl/stringhashtable_tpl.h" #include "../tpl/vector_tpl.h" +#include "../tpl/weighted_vector_tpl.h" #include "../descriptor/groundobj_desc.h" #include "../dataobj/environment.h" @@ -32,6 +33,9 @@ private: /// all such objects static vector_tpl groundobj_typen; + static weighted_vector_tpl* groundobj_list_per_climate; + + static uint16 random_groundobj_for_climate_intern(climate cl); public: static bool register_desc(groundobj_desc_t *desc); @@ -65,9 +69,16 @@ public: void cleanup(player_t *player) OVERRIDE; const groundobj_desc_t* get_desc() const { return groundobj_typen[groundobjtype]; } + static vector_tpl const& get_all_desc() { return groundobj_typen; } + static const groundobj_desc_t *find_groundobj( const char *obj_name ) { return groundobj_typen.empty() ? NULL : desc_table.get(obj_name); } + static bool plant_groundobj_on_coordinate(koord pos, const groundobj_desc_t *desc, const bool check_climate); + static const groundobj_desc_t *random_groundobj_for_climate(climate cl) { uint16 b = random_groundobj_for_climate_intern(cl); return b!=0xFFFF ? groundobj_typen[b] : NULL; } void * operator new(size_t s); void operator delete(void *p); + + static int get_count() { return groundobj_typen.get_count()-1; } + static int get_count(climate cl); }; #endif diff --git a/simmenu.cc b/simmenu.cc index a7dfbe3de..cd2c4bcc3 100644 --- a/simmenu.cc +++ b/simmenu.cc @@ -122,6 +122,7 @@ tool_t *create_general_tool(int toolnr) case TOOL_MERGE_STOP: tool = new tool_merge_stop_t(); break; case TOOL_EXEC_SCRIPT: tool = new tool_exec_script_t(); break; case TOOL_EXEC_TWO_CLICK_SCRIPT: tool = new tool_exec_two_click_script_t(); break; + case TOOL_PLANT_GROUNDOBJ: tool = new tool_plant_groundobj_t(); break; default: dbg->error("create_general_tool()","cannot satisfy request for general_tool[%i]!",toolnr); return NULL; @@ -226,6 +227,7 @@ tool_t *create_dialog_tool(int toolnr) case DIALOG_LIST_DEPOT: tool = new dialog_list_depot_t(); break; case DIALOG_LIST_VEHICLE: tool = new dialog_list_vehicle_t(); break; case DIALOG_SCRIPT_TOOL: tool = new dialog_script_tool_t(); break; + case DIALOG_EDIT_GROUNDOBJ: tool = new dialog_edit_groundobj_t(); break; default: dbg->error("create_dialog_tool()","cannot satisfy request for dialog_tool[%i]!",toolnr); return NULL; diff --git a/simmenu.h b/simmenu.h index da126eb78..f90a9af00 100644 --- a/simmenu.h +++ b/simmenu.h @@ -76,6 +76,7 @@ enum { TOOL_MERGE_STOP, TOOL_EXEC_SCRIPT, TOOL_EXEC_TWO_CLICK_SCRIPT, + TOOL_PLANT_GROUNDOBJ, GENERAL_TOOL_COUNT, GENERAL_TOOL = 0x1000 }; @@ -161,6 +162,7 @@ enum { DIALOG_LIST_DEPOT, DIALOG_LIST_VEHICLE, DIALOG_SCRIPT_TOOL, + DIALOG_EDIT_GROUNDOBJ, DIALOGE_TOOL_COUNT, DIALOGE_TOOL = 0x4000 }; diff --git a/simtool-dialogs.h b/simtool-dialogs.h index 017395b9a..529d3849c 100644 --- a/simtool-dialogs.h +++ b/simtool-dialogs.h @@ -17,6 +17,7 @@ #include "gui/curiosity_edit.h" #include "gui/citybuilding_edit.h" #include "gui/baum_edit.h" +#include "gui/groundobj_edit.h" #include "gui/jump_frame.h" #include "gui/optionen.h" #include "gui/map_frame.h" @@ -521,6 +522,24 @@ public: bool is_work_network_safe() const OVERRIDE{ return true; } }; +/* groundobj placing dialog */ +class dialog_edit_groundobj_t : public tool_t { +public: + dialog_edit_groundobj_t() : tool_t(DIALOG_EDIT_GROUNDOBJ | DIALOGE_TOOL) {} + char const* get_tooltip(player_t const*) const OVERRIDE{ return translator::translate("groundobj builder"); } + image_id get_icon(player_t *) const OVERRIDE { return groundobj_t::get_count() > 0 ? icon : IMG_EMPTY; } + bool is_selected() const OVERRIDE{ return win_get_magic(magic_edit_groundobj); } + bool init(player_t* player) OVERRIDE{ + if (!is_selected()) { + create_win(new groundobj_edit_frame_t(player), w_info, magic_edit_groundobj); + } + return false; + } + bool exit(player_t*) OVERRIDE{ destroy_win(magic_edit_groundobj); return false; } + bool is_init_network_safe() const OVERRIDE{ return true; } + bool is_work_network_safe() const OVERRIDE{ return true; } +}; + // to increase map-size class dialog_enlarge_map_t : public tool_t{ public: diff --git a/simtool.cc b/simtool.cc index e802f9fdc..524d15aee 100644 --- a/simtool.cc +++ b/simtool.cc @@ -2158,6 +2158,62 @@ const char *tool_plant_tree_t::work( player_t *player, koord3d pos ) return NULL; } +char const* tool_plant_groundobj_t::move(player_t* const player, uint16 const b, koord3d const pos) +{ + if (b==0) { + return NULL; + } + if (env_t::networkmode) { + // queue tool for network + nwc_tool_t *nwc = new nwc_tool_t(player, this, pos, welt->get_steps(), welt->get_map_counter(), false); + network_send_server(nwc); + return NULL; + } + else { + return work( player, pos ); + } +} + + +const char *tool_plant_groundobj_t::work( player_t *player, koord3d pos ) +{ + koord k(pos.get_2d()); + + grund_t *gr = welt->lookup_kartenboden(k); + if(gr) { + + const groundobj_desc_t *desc = NULL; + bool check_climates = true; + if(default_param==NULL || strlen(default_param)==0) { + desc = groundobj_t::random_groundobj_for_climate( welt->get_climate( k ) ); + if (desc == NULL) { + return NULL; + } + } + else { + check_climates = default_param[0]=='0'; + desc = groundobj_t::find_groundobj(default_param+3); + } + + // disable placing groundobj on slopes unless they have extra phases (=moving or for slopes) + if( !(gr->get_grund_hang() == sint8(0)) && desc->get_phases() == 2 ) { + return NULL; + } + + // check funds + sint64 const cost = -desc->get_price(); + if( !player->can_afford(cost) ) { + return NOTICE_INSUFFICIENT_FUNDS; + } + if(desc && groundobj_t::plant_groundobj_on_coordinate( k, desc, check_climates ) ) { + player_t::book_construction_costs(player, cost, k, ignore_wt); + return NULL; + } + return ""; + } + return NULL; +} + /** diff --git a/simtool.h b/simtool.h index 2127134cc..9022b3819 100644 --- a/simtool.h +++ b/simtool.h @@ -22,6 +22,7 @@ #include "display/viewport.h" #include "obj/baum.h" +#include "obj/groundobj.h" #include "player/simplay.h" @@ -245,6 +246,18 @@ public: bool is_init_network_safe() const OVERRIDE { return true; } }; +class tool_plant_groundobj_t : public kartenboden_tool_t { +public: + tool_plant_groundobj_t() : kartenboden_tool_t(TOOL_PLANT_GROUNDOBJ | GENERAL_TOOL) {} + image_id get_icon(player_t *) const OVERRIDE { return groundobj_t::get_count() > 0 ? icon : IMG_EMPTY; } + char const* get_tooltip(player_t const*) const OVERRIDE { return translator::translate( "Plant groundobj" ); } + bool init(player_t*) OVERRIDE { return groundobj_t::get_count() > 0; } + char const* move(player_t* const player, uint16 const b, koord3d const k) OVERRIDE; + bool move_has_effects() const OVERRIDE { return true; } + char const* work(player_t*, koord3d) OVERRIDE; + bool is_init_network_safe() const OVERRIDE { return true; } +}; + /* only called directly from schedule => no tooltip! * default_param must point to a schedule! */