Skip to content
Matthew Forrester edited this page Aug 22, 2023 · 3 revisions

message_t:add_message()

This is used to "Add a message to the message list". Declared as a public member function in simmesg.h:

        void add_message( const char *text, koord pos, uint16 what, FLAGGED_PIXVAL color=SYSCOL_TEXT, image_id image=IMG_EMPTY );

Examples of passed parameters:

(const char *text   koord pos,                             uint16 what_flags,      FLAGGED_PIXVAL color=SYSCOL_TEXT, image_id image=IMG_EMPT
(buf,               unlinked_consumer->get_pos().get_2d(), message_t::industry,    CITY_KI,                          unlinked_consumer->get_desc()->get_building()->get_tile(0)->get_background(0, 0, 0))
(buf,               pos.get_2d(),                          message_t::industry,    CITY_KI,                          our_fab->get_desc()->get_building()->get_tile(0)->get_background(0, 0, 0))
(buf,               koord::invalid,                        message_t::new_vehicle, NEW_VEHICLE,                      IMG_EMPTY)
(buf,               koord::invalid,                        message_t::new_vehicle, NEW_VEHICLE,                      desc->get_image_id(5,0))
(translator::translate("Not enough money to carry out essential way renewal work.\n"),
                    get_pos().get_2d(),                    message_t::warnings,    owner->get_player_nr()            )
(buf.get_str(),     koord::invalid,                        flag,                   color,                            IMG_EMPTY )
(buf,               koord::invalid,                        warning_message_type,   player_nr,                        IMG_EMPTY )
((const char *)buf, cnv->get_pos().get_2d(),               message_t::problems,    PLAYER_FLAG | player_nr,          cnv->front()->get_base_image())
(text,              pos,                                   message_t::scenario,    PLAYER_FLAG|welt->get_active_player()->get_player_nr())
(buf,               best_pos + koord(1, 1),                message_t::city,        CITY_KI,                          desc->get_tile(0)->get_background(0, 0, 0))
(buf,               koord::invalid,                        message_t::city,        COL_GROWTH                        )
(buf,               home_depot.get_2d(),                   message_t::general,     PLAYER_FLAG|get_owner()->get_player_nr(),
                                                                                                                     IMG_EMPTY)
(buf,               pos.get_2d(),                          message_t::industry,    color_idx_to_rgb(COL_DARK_RED),   skinverwaltung_t::neujahrsymbol->get_image_id(0))
(buf,               pos.get_2d(),                          message_t::industry,    CITY_KI,                          skinverwaltung_t::neujahrsymbol->get_image_id(0))
(buf,               fab->get_pos().get_2d(),               message_t::industry,    CITY_KI,                          fab->get_desc()->get_building()->get_tile(0)->get_background(0, 0, 0))
((const char *)buf, ziel.get_2d(),                         message_t::warnings,    PLAYER_FLAG | cnv->get_owner()->get_player_nr(),
                                                                                                                     cnv->front()->get_base_image() )
(translator::translate("To heavy traffic\nresults in traffic jam.\n"),
                    get_pos().get_2d(),                    message_t::traffic_jams|message_t::expire_after_one_month_flag, 
                                                                                   color_idx_to_rgb(COL_ORANGE) )

Each of the parameters requires careful consideration.

const char *text

This is a pointer to read-only text (in an array of?) chars.

koord pos

This is the "position of the event" on the map, in 2D co-ordinates. Although VSCode directed me elsewhere, the #includes in simmesg.h make me think that this is declared in dataobj/koord.h. I was somehow thinking of koords as being a simpler implementation of a type, but here I learn that we are dealing with a class koord with public member variabels sint16 x and y. So this is going to copy the passed instance into a new instance used by this function (confirmed by Stack Overflow).

uint what_flags

The declaration has what. This is different from what_flags used in simmesg.cc. The names of parameters in declarations have no actual effect and are merely a hint to people reading the code. But it's a funny typo or cut_and_paste error to make. After spending some wrestling with this parameter, I am not certain the name should be read as "what flags", as in "what flags are set?", but as "what+flags", because it combines both the "type of message" (as per the comment) and, the boolean flags set for this message, in a single variable. It's a uint16.

message_t::node::msg_type

The key to this appears to be in message_t::node::msg_type, an enum , which is declared as follows:

	enum msg_typ {
		general      = 0,
		ai           = 1,
		city         = 2,
		problems     = 3,
		industry     = 4,
		chat         = 5,
		new_vehicle  = 6,
		full         = 7,
		warnings     = 8,
		traffic_jams = 9,
		scenario     = 10,
		MAX_MESSAGE_TYPE,
		MESSAGE_TYPE_MASK = 0xf,

		expire_after_one_month_flag = 1 << 13,
		do_not_rdwr_flag            = 1 << 14,
		playermsg_flag              = 1 << 15
	};

So it looks like we have a bitfield in the three most significant bits used here and an enum of the kind I'm used to in the four least significant bits (though not all are used). At this point, I am somewhat puzzled as so how the two relate though. For example, the second least significant bit would be set only for 2 (city), 4 (industry), 5 (chat) and 10 (scenario); it's not obvious to me what the common factor is there.

And I can't be absolutely sure of this: it's possible that "type of message" is explained by "msg_typ", but I can't be certain, since message_t also includes another set of "bitfields that contains the messages" containing ticker_flags and others, so I will have to look in the definition to be sure.

what_bit

And indeed, this is the second thing [in the definition]((https://github.com/jamespetts/simutrans-extended/blob/210bfad57f3dbb8ac3c6cbd88787efaf039dc5b4/simmesg.cc#L119) (after a brief debug message):

	sint32 what_bit = 1<<(what_flags & MESSAGE_TYPE_MASK);

The signed (?) 32-bit local variable what_bit is set by bitwise arithmetic. The part in brackets is a standard bit mask check and since it refers back to MESSAGE_TYPE_MASK we can now be sure that what_flags/what is intended to be filled with the items from the enum. MESSAGE_TYPE_MASK is 0b1111, which has the particular property of preserving only the four least significant digits. Right now I am most interested in industry, which is 4 or 0b0110. 1<<0b0110 gets us 0b1100 as what_bit.

ignore_flags

	sint32 what_bit = 1<<(what_flags & MESSAGE_TYPE_MASK);
	if(  what_bit&ignore_flags  ) {
		// wants us to ignore this completely
		return;
	}

The function continues on the next line, but really this is the start of a subsection. We apply another bitmask, ignore_flags, which is declared back in the header file as a sint32(!) with the comment "bitfields that contains the messages". "Contains" here can't have its normal English meaning, so it could be a special C++ meaning or it could be a mistranslation from German. It is initialized in the constructor as 0. If we calculate 0b1100 & 0b0000 then we get 0b0000, so the if test produces false and the function proceeds. But presumably there is a case where the test produces true and the ambiguous comment "wants us to ignore this completely" doesn't really explain what this is.

Traffic jam messages

The next chunk of code continues to use what_bit:

	/* we will not add traffic jam messages two times to the list
	 * if it was within the last 20 messages
	 * or within last months
	 * and is not a general (BLACK) message
	 */
	if(  what_bit == (1<<traffic_jams)  ) {
		sint32 now = welt->get_current_month()-2;
		uint32 i = 0;
		FOR(slist_tpl<node*>, const iter, list) {
			node const& n = *iter;
			if (n.time >= now &&
					strcmp(n.msg, text) == 0 &&
					(n.pos.x & 0xFFF0) == (pos.x & 0xFFF0) && // positions need not 100% match ...
					(n.pos.y & 0xFFF0) == (pos.y & 0xFFF0)) {
				// we had exactly this message already
				return;
			}
			if (++i == 20) break;
		}
	}

Now the first line of this is illuminating. traffic_jams is 9. So the test is true if (1<<9), i.e. in a uint16 0000 0010 0000 0000. That suggests that all the values in message_t::msg_typ are to be interpreted as bitfield flags. So a traffic jam message's what/what_flags parameter would be 0000 0010 0000 0000 not 0000 0000 0000 1001. That makes much more sense. But a Stack Overflow answer sugggests that this does require the bitshift operator.

FLAGGED PIXVAL color=SYSCOL_TEXT

This is the "message color". Simcolor.h declares this as a typedef of an uint with the comment "PIXVAL with above flags (eg. transparent) (uint32).[#Appendix-1-Typedefs] The aforementioned flags relate to transparency, player colours, and outlines, so perhaps this is where the magic happens that converts a particular colour in a .png into a player colour.

image_id image=IMG_EMPTY

This parameter is for the "image associated with message (will be ignored if pos!=koord::invalid)". I presume it's used for icons that appear in message windows, but we shall see. It's another typedef in display/simimg.h, on this occasion a uint32.


welt->get_message()

This is declared in simworld.h's karte_t as a public member function:

	/**
	 * Returns the messagebox message container.
	 */
	message_t *get_message() const { return msg; }

This is a read-only pointer to another pointer:

/**
	 * Holds all the text messages in the messagebox (chat, new vehicle etc).
	 */
	message_t *msg;

The message box instance is created early in the karte_t constructor.


Messages referring to a city

An example of this is found in simcity.cc's stadt_t::check_bau_spezial() function. Note that it is a member ofstadt_t, which may limit the applicability of the approach used here for our cases. Let's study line by line:

				gebaeude_t* gb = hausbauer_t::build(owner, welt->lookup_kartenboden(best_pos)->get_pos(), rotate, desc);

Create a pointer of type gebaude_t (i.e. building) and make it point to the best place to build a building. A very helpful comment explains that hausbauer_t::build() returns "The first built part of the building. Usually at pos, if this building tile is not empty".

				gb->access_first_tile()->set_stadt(this);

For the building pointed to by gb, use the member function gebaude_t::access_first_tile to get a pointer to its 'dominant' or 'reference' tile. According to gebaude.h, this "@returns pointer to first tile of a multi-tile building". For the 'dominant' tile pointed to by that function, use the member function gebaude_t::set_stadt, whichs "sets the corresponding city" for that pointer. There is some behind-the-scenes stuff here that happens with a special gebaude_t::ptr union that can point to either a factory or a stadt, which is disappointing as it steepens the learning curve a little further.

I expected this line to either set a stadt member variable for the building object pointed to by gb or possibly set a pointer to the stadt_t object relevant to the tile(s) used by the building pointed to by gb. Unfortunately, right now I am not sure which of those it's doing. :-(

				add_building_to_list(gb->access_first_tile());

Unfortunately stadt_t::add_building_to_list() has no comments at all. It is very likely that this is a list of all the buildings in the city though. The first parameter for this function is gebaude_t* building; what is passed to it here is a pointer to the 'dominant' tile of gb. I guess the tile pointer is being used as to identify buildings, though why we are using a pointer to a tile instead of a pointer to the building object as a whole or simply the (x,y) pos is not obvious to me in my newbie ignorance.

NOTE: This function is a member of stadt_t. That may matter.

				// tell the player, if not during initialization
				if (!new_town) {
					cbuffer_t buf;
					buf.printf( translator::translate("To attract more tourists\n%s built\na %s\nwith the aid of\n%i tax payers."), get_name(), make_single_line_string(translator::translate(desc->get_name()), 2), city_history_month[0][HIST_CITIZENS]);
					welt->get_message()->add_message(buf, best_pos + koord(1, 1), message_t::city, CITY_KI, desc->get_tile(0)->get_background(0, 0, 0));
				}

Example in factory_builder_t::increase_industry_density()

"This method is called whenever it is time for industry growth." This function is found in fabrikbauer.cc . We are interested in it because the functions determines a factory is needed and has built one, it has a section to "tell the player" in a message.

						if(tell_me) {

This line tests for a boolean function parameter. It is true in every call except when karte_t::init() is creating a new map.

							stadt_t *s = welt->find_nearest_city(pos.get_2d());

This line defines a pointer s of type stadt_t (so a pointer to a town) by calling karte_t::find_nearest_city() and passing co-ordinates obtained by calling the get_2d() member function of koord3d pos. pos was set back in Lines 1428 or 1434 (depending on whether the co-ordinate is in a city limit or not), using factory_builder_t::find_random_construction_site(). It was defined back in L1423 by using karte_t::lookup_kartenboden() and get_pos(), chained with arrow operators.

							const char *stadt_name = s ? s->get_name() : translator::translate("nowhere");

This line uses the ternary operator to initialize a read-only pointer to a character array, stadt_name. If the pointer s is TRUE (which, in this case, presumably equates to "not NULL"), then it uses stadt_t::get_name() with the arrow operator on the object pointed to by s, presmuably giving the name of that city. If the pointer s is FALSE (which, in this case, presumably equates to "NULL"?), then it sets the array using the internationalized text "nowhere" via translator::translate(). This isn't defined in en.tab though, nor can grep find it in ...simutrans-extended/simutrans/text, which is surprising.

							cbuffer_t buf;

This creates the character buffer of type "cbuffer_trequired byprintf()`.

							buf.printf( translator::translate("New factory chain\nfor %s near\n%s built with\n%i factories."), translator::translate(our_fab->get_name()), stadt_name, nr );

This calls the cbuffer_t::printf() member function on the translation of the given text (which in Engish is "A new industry chain for %s near %s has been built with %i industries." The variables are not supplied directly, but also through translator::translate().

The first string variable is obtained by calling the member function fabrik_t::get_name() ("if name==NULL translate desc factory name in game language", though the code makes clear that should be !=NULL) on our_fab. The latter is a pointer to an object of type fabrik_t, which was set in L1440 as follows:

                                                                                               fabrik_t *our_fab = fabrik_t::get_fab( pos.get_2d() );

The second string variable is the pointer `stadt_name", which we have just discussed.

The (signed) integer variable is nr which is a counter declared at the start of the function in L1134 and increased in L1438 every time the chain is extended.

							welt->get_message()->add_message(buf, pos.get_2d(), message_t::industry, CITY_KI, our_fab->get_desc()->get_building()->get_tile(0)->get_background(0, 0, 0));
						}

Now we send the character to the buffer to the message container. The add_message() parameters are set as follows

const char *text                    buf
koord pos                           pos.get_2d()
uint16 what / what_flags            message_t::industry
FLAGGED_PIXVAL color=SYSCOL_TEXT    CITY_KI
image_id image=IMG_EMPTY            our_fab->get_desc()->get_building()->get_tile(0)->get_background(0, 0, 0)

This all seems in line with expectations set by our study of that function.


Appendix 1 Typedefs

A typedef declares an alias for type. Note that compilers see through this and won't prevent you doing the following:

typedef uint32 horse_height; # hands
typedef uint32 cat_height; # cm
horse_height my_pet_height;
cat_height my_sisters_pet_height;
if(my_pet_height == my_sisters_pet_height) {...

Comparing hand and centimetre types is probably not what the coder intended, but all the compiler sees is uint32s so won't warn you.

Appendix 2 Bitfields and endianness

I suddenly wondered whether bitfields are affected by endianness. I had been presuming that the least significant bits are the rightmost ones. According to Stack Overflow, I can safely assume this in C++ and it seems that most discussions of endianness are assumed to be byte-level only, even though bitwise endianness is indeed a thing.

Appendix 3 ignore_flags

There must be a case in which ignore_flags causes the test to fail. Since this bitmask is 0b0000, we need a value of what_bit that produces a true result. But any value of what_bit will produce 0 if *&ed with 0000. But set_message_flags() seems to allow ignore_flags to be set. So maybe when it's set, then we get meaningful values that cause the test to fail and abort the function. For the time being, I'm going to ignore ignore_flags until I need it.