Freeciv Hacker's Guide This guide is intended to be a help for developers, wanting to mess with Freeciv program. Here and there, you'll see some comments marked as [...], containing more personal thoughts on the design, why it looks like it does, and sometimes what went wrong. I hope developers will find that interesting too. =========================================================================== Basic =========================================================================== Freeciv is a client/server civilization style of game, written in C. The client is pretty dumb. Almost all calculations is performed on the server. [It wasn't like this always. Originally more code was placed in the common/ dir, allowing the client to do some of the world updates itself. The end_of_turn city-refresh was for example performed both on the server and on the client. However things got quite complex, more and more info was needed on the client-side(security problem). Little by little we moved more code to the server, and as of 1.5 the client is quite dumb -PU] =========================================================================== Server =========================================================================== General: The server main loop basically looks like: while(server_state==RUN_GAME_STATE) { /* looped once per turn */ do_ai_stuff(); /* do the ai controlled players */ sniff_packets(); /* get player requests and handle them */ end_turn(); /* main turn update */ game_next_year(); } Most time is spend in the sniff_packets() function, where a select() call waits for packets or input on stdin(server-op commands). =========================================================================== Datastructures =========================================================================== If you study the code, you'll soon come upon the genlist constructs. The genlist is a simple double-linked list, into which any kind of data can be inserted. The basic genlist is only used in a few places, as it's completely type-unsafe(void*). However the genlist is used as a "base-class" for some typesafe versions, which are used all over the program. unit_list is just one example: struct unit_list { struct genlist list; }; Here's an example of how to use an iterator, to scan a genlist: { struct unit_list *punit_list; struct genlist_iterator myiter; ..... genlist_iterator_init(&myiter, &punit_list->list, 0); for(; ITERATOR_PTR(myiter); ITERATOR_NEXT(myiter)) { punit=(struct unit *)ITERATOR_PTR(myiter); if(unit_can_defend_here(punit) && rate_unit(punit, aunit)>bestvalue) { bestvalue=rate_unit(punit, aunit); bestdef=punit; } } } First the iterator is initialized with the list. ITERATOR_PTR(myiter) is a macro, returning a void* to the element pointed to by the iterator, NULL if the iterator doesn't point to an element. ITERATOR_NEXT(myiter) moves the iterator one element forward. Careful! The above idiom with the for-construct can be pretty lethal! If you inside the for-loop removes the element pointed to by myiter, then ITERATOR_NEXT(myiter) will probably return garbage. The following example shows a correct way of removing genlist elements while iterating through the list. for(; ITERATOR_PTR(myiter);) { struct unit *punit2=(struct unit *)ITERATOR_PTR(myiter); ITERATOR_NEXT(myiter); /* !!! */ send_remove_unit(0, punit2->id); game_remove_unit(punit2->id); } As of 1.5.3 a number of high-level macros for scanning genlists have been made. The above loop can now be written as: unit_list_iterate(punit_list, punit) send_remove_unit(0, punit->id); game_remove_unit(punit->id); unit_list_iterate_end; [The genlist was in theory a good idea. However the implementation turned out quite crappy. The typecasting shouldn't have been necessary and using some more macro-magic, the much-used scan_all_the_list case could have been handled much more elegant. Well Claus just did this for 1.5.3 -PU] ========================================================================= Network and packets ========================================================================= The basic netcode is located in server/sernet.c and client/clinet.c. Notice the XtAppAddInput() call in client, that tells Xt to call the callback functions, when something happens on the client socket. All information passed between the server and clients, is send as serialized packet structures. These packets structures are all defined in common/packets.h. For each packet, there's two corresponding send and receive functions: int send_packet_unit_combat(struct connection *pc, struct packet_unit_combat *packet); struct packet_unit_combat *recieve_packet_unit_combat(struct connection *pc); Each of these functions (de-)serializes a bytestream representation of the corresponding packet. Each entry in a packet-structure is (de-)serialized using architecture independent function such as get_int32() and put_int32(). To demonstrate the route for a packet through the system, here's how a unit disband is performed. 1) A player disbands a unit. 2) The client initializes a packet_unit_request structure, and calls the packet layer with this structure and packet type: PACKET_UNIT_DISBAND 3) The packet layer serializes the structure, wraps it up in a packet containing the type, the packetlength and the serialized data. Finally the data is send to the server. 4) On the server the packet is read. Based on the type, the corresponding deserialize function is called. 5) a packet_unit_request is initialized with the bytestream. 6) Finally the corresponding packet-handler(handle_unit_disband) function is called with the newly constructed structure. 7) The handler function checks if the disband request is legal(is the sender really the owner of the unit) etc. 8) The unit is disbanded=> wipe_unit() => send_remove_unit(). 9) Now an integer, containing the id of the disbanded unit is wrapped into a packet along with the type PACKET_REMOVE_UNIT: send_packet_generic_integer 10) The packet is serialized and send across the network 11) On the client the packet is deserialized into a packet_generic_integer structure 12) The corresponding client handler function is now called handle_remove_unit(), and finally the unit is disbanded. Notice that both packets involved in this scenery was generic packets. That is the packet structures involved, are used in various requests and updated. The packet_unit_request is for example also used for the packets PACKET_UNIT_BUILD_CITY and PACKET_UNIT_CHANGE_HOMECITY. When adding a new packet type, then check to see if you can reuse some of the existing packet types. This saves you the trouble of writing new serialize/deserialize functions. [The packet system started out as a pain and it stayed that way all the time. After using Java's object serialize system, this handcoded serialize code really feels like the stoneage. That said, I fear it requires some advanced pre-processing system etc to automate the task for C structures. -PU] ========================================================================= Graphics ========================================================================= Currently the graphics is stored in the xpm fileformat, and accordingly loaded using libxpm. If you alter the graphics, then notice that after saving the new xpm file, you must manually mark the first color as transparent. Look at the original xpm-files, to see how this is done. Failing to do this results in crash(the mask-pixmaps will not be generated). Each terrain tile is drawn in 16 versions, all the combinations with with a green border in one of the main directions. Hills, mountains, forests and rivers are treated in special cases. [IMO we should have gotten rid of libxpm long ago. The graphics should be drawn in 24bit, and then quantized runtime, if the client is displayed on a pseudocolor display. The current tile system should be converted to something like civ2's. They get away with drawing way less tiles, and it looks better. -PU] ========================================================================= Diplomacy ========================================================================= A few words about the diplomacy system. When a diplomacy meeting is established, a Treaty structure is created on both of the clients and on the server. All these structures are updated concurrently as clauses are added and removed. [There might be a bug somewhere in this system, if a player involved in diplomacy loses link. I'm not sure the server Treaty is removed, making it impossible to establish a new Treaty between the two players -PU] ========================================================================= Map ========================================================================= The map is maintained in a pretty straightforward C array, containing X*Y tiles. However the 'known' field needs a comment. struct tile { ... unsigned short known; ... }; One the server the known fields is considered to be a bitvector, one bit for each player, 0==tile unknown, 1==tile known. On the client this field contains one of the following 3 values: enum known_type { TILE_UNKNOWN, TILE_KNOWN_NODRAW, TILE_KNOWN }; The values TILE_UNKNOWN, TILE_KNOWN is straightforward. TILE_KNOWN_NODRAW however is a special value, given to all unknown tiles, sharing border with a known. Because of how the tile-drawing system works, the client has to know the terrain and specials of such tiles. [If I were to recode this, I'd drop the ugly TILE_KNOWN_NODRAW value, and have the server tell me exactly which kind of graphics should be drawn at each tile. -PU] ========================================================================= Client GUI- Athena ========================================================================= The client GUI is written using athena-widgets. A few comments on this could prove useful for anyone wishing to write new dialogs or improve on the current ones. Widgets: -------- When you create new widgets for a dialog, like: players_form = XtVaCreateManagedWidget("playersform", formWidgetClass, players_dialog_shell, NULL); then put the widget properties in the app-default file 'Freeciv', instead of hardcoding them. For the widget created above, the following entries in the app-default file applies: *playersform.background: lightblue *playersform.resizable: true *playersform.top: chainTop *playersform.bottom: chainBottom *playersform.left: chainLeft *playersform.right: chainRight Pixcomm and Canvas: ------------------- The Pixcomm is a subclassed Command-widget, which can displays a Pixmap instead of a string, on top of a button(command). The Pixcomm widget should be used all places where this kind of high-level functionality is required. The Canvas widget is more low-level. One have to write an expose(redraw) event-handler for each widget. The widget generates events on resize and mousebuttons. [Reading any Xt documentation, will tell you how powerful widget subclassing is. So I went trough great troubles subclassing the command widget. It was not before long I got mails from unhappy Xaw3d (and derives) users, that the client keeps crashing on them. Turns out that subclassing from any widgets but Core, chains the new widgets to libXaw. In hindsight I should just subclassed the Canvas widget and add more highlevel functionality. -PU] =========================================================================== Misc - The idea trashcan =========================================================================== [Currently all of the major entities - units, cities, players, contains an unique id. This id is really only required when a reference to an entity is to be serialized(saved or distributed over the net). However in the current code, the id is also used for comparing, looking up and in general referencing entities. This results in a lot of mess and unnecessary duplicate functions. Often when programming, one wonders if some function needs the id or a pointer when referring to an entity. -PU] [Currently there's a 1-to-1 mapping between players and connections. Some interesting results could be achieved, if more connections could be attached to a single player(race). That way 2 persons could play cooperate, splitting the work between them. -PU]