Complete.Org: Mailing Lists: Archives: freeciv-dev: February 2006:
[Freeciv-Dev] Re: (PR#13605) New civworld-in-client patch
Home

[Freeciv-Dev] Re: (PR#13605) New civworld-in-client patch

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
Subject: [Freeciv-Dev] Re: (PR#13605) New civworld-in-client patch
From: "Jason Short" <jdorje@xxxxxxxxxxxxxxxxxxxxx>
Date: Mon, 13 Feb 2006 13:34:15 -0800
Reply-to: bugs@xxxxxxxxxxx

<URL: http://bugs.freeciv.org/Ticket/Display.html?id=13605 >

Jason Short wrote:
> <URL: http://bugs.freeciv.org/Ticket/Display.html?id=13605 >
> 
> Per I. Mathisen wrote:
> 
>><URL: http://bugs.freeciv.org/Ticket/Display.html?id=13605 >
>>
>>This is just updated to most recent svn. However, I would like suggest
>>that we that one last look at this and then put it in the repository, as
>>this is _the_ feature for 2.2 and everyone should ideally be helping out
>>on it.
> 
> I agree.  However the patch is just too buggy to commit yet.  We should 
> #if 0 out the broken parts and commit the rest.

In this version:

- I fixed a bunch of fatal bugs.
- It allows editing by observers (see srv_main.c).

As far as I'm concerned this can be committed.  The only major 
shortcomings (aside from lack of features) are that

1.  The server should remember when editing has been done, store it in 
the savegame, and report it at end-game.

2.  The client needs to provide a confirmation dialog before entering 
edit mode.

-jason

Index: server/srv_main.c
===================================================================
--- server/srv_main.c   (revision 11583)
+++ server/srv_main.c   (working copy)
@@ -1027,18 +1027,20 @@
   }
   
   /* valid packets from established connections but non-players */
-  if (type == PACKET_CHAT_MSG_REQ) {
-    handle_chat_msg_req(pconn,
-                       ((struct packet_chat_msg_req *) packet)->message);
+  if (type == PACKET_CHAT_MSG_REQ
+      || type == PACKET_SINGLE_WANT_HACK_REQ
+      || type == PACKET_EDIT_MODE
+      || type == PACKET_EDIT_TILE
+      || type == PACKET_EDIT_UNIT
+      || type == PACKET_EDIT_CITY
+      || type == PACKET_EDIT_PLAYER) {
+    if (!server_handle_packet(type, packet, pplayer, pconn)) {
+      freelog(LOG_ERROR, "Received unknown packet %d from %s",
+             type, conn_description(pconn));
+    }
     return TRUE;
   }
 
-  if (type == PACKET_SINGLE_WANT_HACK_REQ) {
-    handle_single_want_hack_req(pconn,
-                               (struct packet_single_want_hack_req *) packet);
-    return TRUE;
-  }
-
   pplayer = pconn->player;
 
   if(!pplayer) {
Index: server/cityturn.c
===================================================================
--- server/cityturn.c   (revision 11583)
+++ server/cityturn.c   (working copy)
@@ -523,6 +523,32 @@
   sync_cities();
 }
 
+/****************************************************************************
+  Change the city size.  Return TRUE if the city is still alive afterwards.
+****************************************************************************/
+bool city_change_size(struct city *pcity, int size)
+{
+  assert(size >= 0 && size <= MAX_CITY_SIZE);
+
+  if (size > pcity->size) {
+    while (size > pcity->size) {
+      const int old_size = pcity->size;
+
+      /* city_increase_size can silently fail. Don't get in an infinite
+       * loop. */
+      city_increase_size(pcity);
+      if (pcity->size <= old_size) {
+       return TRUE;
+      }
+    }
+    return TRUE;
+  } else if (size < pcity->size) {
+    return city_reduce_size(pcity, pcity->size - size);
+  } else {
+    return TRUE;
+  }
+}
+
 /**************************************************************************
   Check whether the population can be increased or
   if the city is unable to support a 'settler'...
Index: server/cityturn.h
===================================================================
--- server/cityturn.h   (revision 11583)
+++ server/cityturn.h   (working copy)
@@ -27,6 +27,7 @@
 void auto_arrange_workers(struct city *pcity); /* will arrange the workers */
 void apply_cmresult_to_city(struct city *pcity, struct cm_result *cmr);
 
+bool city_change_size(struct city *pcity, int new_size);
 bool city_reduce_size(struct city *pcity, int pop_loss);
 void send_global_city_turn_notifications(struct conn_list *dest);
 void send_city_turn_notifications(struct conn_list *dest, struct city *pcity);
Index: server/gamehand.c
===================================================================
--- server/gamehand.c   (revision 11583)
+++ server/gamehand.c   (working copy)
@@ -500,8 +500,7 @@
 the file values. Sends an answer to the client once it's done.
 **************************************************************************/
 void handle_single_want_hack_req(struct connection *pc,
-                                const struct packet_single_want_hack_req
-                                *packet)
+                                struct packet_single_want_hack_req *packet)
 {
   struct section_file file;
   char *token = NULL;
Index: server/gamehand.h
===================================================================
--- server/gamehand.h   (revision 11583)
+++ server/gamehand.h   (working copy)
@@ -30,8 +30,4 @@
 
 struct packet_single_want_hack_req;
 
-void handle_single_want_hack_req(struct connection *pc,
-                                const struct packet_single_want_hack_req
-                                *packet);
-
 #endif  /* FC__GAMEHAND_H */
Index: server/edithand.c
===================================================================
--- server/edithand.c   (revision 0)
+++ server/edithand.c   (revision 0)
@@ -0,0 +1,343 @@
+/********************************************************************** 
+ Freeciv - Copyright (C) 2005 - The Freeciv Project
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+***********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+
+#include "events.h"
+#include "fcintl.h"
+#include "log.h"
+#include "shared.h"
+#include "support.h"
+
+#include "game.h"
+#include "government.h"
+#include "map.h"
+#include "movement.h"
+#include "terrain.h"
+
+#include "citytools.h"
+#include "cityturn.h"
+#include "gamehand.h"
+#include "plrhand.h"
+#include "unittools.h"
+#include "hand_gen.h"
+#include "maphand.h"
+
+/****************************************************************************
+  Handles new tile information from the client, to make local edits to
+  the map.
+****************************************************************************/
+void handle_edit_mode(struct connection *pc, bool is_edit_mode)
+{
+  if (pc->access_level != ALLOW_HACK) {
+    return;
+  }
+  if (!game.info.is_edit_mode && is_edit_mode) {
+    /* Someone could be cheating! Warn people. */
+    notify_conn(NULL, NULL, E_SETTING,
+               _(" *** Server set to edit mode! *** "));
+  }
+  if (!EQ(game.info.is_edit_mode, is_edit_mode)) {
+    game.info.is_edit_mode = is_edit_mode;
+    send_game_info(NULL);
+  }
+}
+
+/****************************************************************************
+  Handles new tile information from the client, to make local edits to
+  the map.
+
+  TODO: Handle resources.
+****************************************************************************/
+void handle_edit_tile(struct connection *pc, int x, int y,
+                      Terrain_type_id terrain, Resource_type_id resource,
+                     bv_special special)
+{
+  struct tile *ptile = map_pos_to_tile(x, y);
+  struct terrain *pterrain = get_terrain(terrain), *old_terrain;
+  struct resource *presource = get_resource(resource);
+
+  if (pc->access_level != ALLOW_HACK || !game.info.is_edit_mode
+      || !ptile || !pterrain) {
+    return;
+  }
+
+  old_terrain = ptile->terrain;
+
+  specials_iterate(s) {
+    if (contains_special(special, s) && !tile_has_special(ptile, s)) {
+      tile_add_special(ptile, s);
+    } else if (!contains_special(special, s) && tile_has_special(ptile, s)) {
+      tile_remove_special(ptile, s);
+    }
+  } specials_iterate_end;
+
+  tile_set_resource(ptile, presource); /* May be NULL. */
+
+  tile_change_terrain(ptile, pterrain);
+
+  /* Handle global side effects. */
+  check_terrain_change(ptile, old_terrain);
+
+  /* update playertiles and send updates to the clients */
+  update_tile_knowledge(ptile);
+  send_tile_info(NULL, ptile);
+}
+
+/****************************************************************************
+  Handles unit information from the client, to make edits to units.
+
+  FIXME: We do some checking of input but not enough.
+****************************************************************************/
+void handle_edit_unit(struct connection *pc, struct packet_edit_unit *packet)
+{
+  struct tile *ptile = map_pos_to_tile(packet->x, packet->y);
+  struct unit_type *punittype = get_unit_type(packet->type);
+  struct player *pplayer = get_player(packet->owner);
+  struct unit *punit;
+
+  if (pc->access_level != ALLOW_HACK || !game.info.is_edit_mode
+      || !ptile || !punittype || !pplayer
+      || (packet->create_new && packet->delete)) {
+    return;
+  }
+
+  /* check if a unit with this id already exists on this tile, if
+   * so, then this is an edit, otherwise, we create a new unit */
+  if (packet->create_new) {
+    struct city *homecity
+      = player_find_city_by_id(pplayer, packet->homecity);
+
+    if (is_non_allied_unit_tile(ptile, pplayer)) {
+      notify_player(pplayer, ptile, E_BAD_COMMAND,
+                    _("Cannot create unit on enemy tile."));
+      return;
+    }
+    /* FIXME: should use can_unit_exist_at_tile here. */
+    if (!(ptile->city
+         && !(is_sailing_unittype(punittype)
+              && !is_ocean_near_tile(ptile)))
+       && !is_native_terrain(punittype, ptile->terrain)) {
+      notify_player(pplayer, ptile, E_BAD_COMMAND,
+                    _("Cannot create %s unit on this terrain."),
+                    punittype->name);
+      return;
+    }
+
+    punit = create_unit(pplayer, ptile, punittype,
+                        packet->veteran,
+                       homecity ? homecity->id : 0,
+                       packet->movesleft);
+  } else {
+    punit = find_unit_by_id(packet->id);
+    if (!punit) {
+      freelog(LOG_ERROR, "can't find unit to edit!");
+      return;
+    } 
+  }
+
+  if (packet->delete) {
+    wipe_unit(punit);
+    return;
+  }
+
+  punit->hp = CLIP(0, packet->hp, punittype->hp);
+  punit->activity_count = packet->activity_count;
+  punit->fuel = CLIP(0, packet->fuel, punittype->fuel);
+  punit->paradropped = BOOL_VAL(packet->paradropped);
+  if (find_unit_by_id(packet->transported_by)) {
+    punit->transported_by = packet->transported_by;
+  } else {
+    punit->transported_by = -1;
+  }
+
+  /* FIXME: resolve_unit_stacks? */
+  /* FIXME: refresh homecity? */
+
+  /* update playertiles and send updates to the clients */
+  update_tile_knowledge(ptile);
+  send_unit_info(NULL, punit);
+}
+
+/****************************************************************************
+  We do some checking of input but not enough.
+****************************************************************************/
+void handle_edit_city(struct connection *pc, struct packet_edit_city *packet)
+{
+  struct tile *ptile = map_pos_to_tile(packet->x, packet->y);
+  struct city *pcity;
+  struct player *pplayer = get_player(packet->owner);
+  int i;
+  int old_traderoutes[NUM_TRADEROUTES];
+
+  if (pc->access_level != ALLOW_HACK || !game.info.is_edit_mode
+      || !pplayer || !ptile) {
+    return;
+  }
+
+  pcity = tile_get_city(ptile);
+  if (!pcity) {
+    if (!city_can_be_built_here(ptile, NULL)) {
+      notify_player(pplayer, ptile, E_BAD_COMMAND,
+                    _("Cannot build city on this tile."));
+      return;
+    }
+
+    /* new city */
+    create_city(pplayer, ptile, city_name_suggestion(pplayer, ptile));
+    pcity = tile_get_city(ptile);
+
+    if (!pcity) {
+      notify_player(pplayer, ptile, E_BAD_COMMAND,
+                   _("Could not create city."));
+      return;
+    }
+  }
+
+  if (!city_change_size(pcity, CLIP(0, packet->size, MAX_CITY_SIZE))) {
+    /* City died. */
+    return;
+  }
+
+  /* FIXME: should probably be a different packet_edit_trade_route. */
+  for (i = 0; i < NUM_TRADEROUTES; i++) {
+    old_traderoutes[i] = pcity->trade[i];
+  }
+  for (i = 0; i < NUM_TRADEROUTES; i++) {
+    struct city *oldcity = find_city_by_id(old_traderoutes[i]);
+    struct city *newcity = find_city_by_id(packet->trade[i]);
+
+    /*
+     * This complicated bit of logic either deletes or creates trade routes.
+     *
+     * FIXME: What happens if (oldcity && newcity && oldcity != newcity) ?
+     */
+    if (oldcity && !newcity) {
+      remove_trade_route(pcity, oldcity);
+    } else if (newcity && !oldcity && can_cities_trade(pcity, newcity)) {
+      establish_trade_route(pcity, newcity);
+    }
+  }
+
+  pcity->food_stock = MAX(packet->food_stock, 0);
+  pcity->shield_stock = MAX(packet->shield_stock, 0);
+
+  pcity->did_buy = packet->did_buy;
+  pcity->did_sell = packet->did_sell;
+  pcity->was_happy = packet->was_happy;
+  pcity->airlift = packet->airlift;
+
+  pcity->turn_last_built = CLIP(0, packet->turn_last_built, game.info.turn);
+  pcity->turn_founded = CLIP(0, packet->turn_founded, game.info.turn);
+  pcity->before_change_shields = MAX(packet->before_change_shields, 0);
+  pcity->disbanded_shields = MAX(packet->disbanded_shields, 0);
+  pcity->caravan_shields = MAX(packet->caravan_shields, 0);
+  pcity->last_turns_shield_surplus
+    = MAX(packet->last_turns_shield_surplus, 0);
+
+  /* FIXME: Might want to check these values. */
+  pcity->changed_from.is_unit = packet->changed_from_is_unit;
+  pcity->changed_from.value = packet->changed_from_id;
+
+  /* make everything sane.  Note some refreshes may already have been
+   * done above. */
+  city_refresh(pcity);
+
+  /* send update back to client */
+  send_city_info(NULL, pcity);  
+}
+
+/**************************************************************************
+ right now there are no checks whatsoever in the server. beware.
+***************************************************************************/
+void handle_edit_player(struct connection *pc, 
+                        struct packet_edit_player *packet)
+{
+  struct player *pplayer = get_player(packet->playerno);
+  struct nation_type *pnation = get_nation_by_idx(packet->nation);
+  struct team *pteam = team_get_by_id(packet->team);
+  struct government *pgov = get_government(packet->government);
+#if 0 /* Unused: see below */
+  struct player_research *research;
+  int i;
+#endif
+
+  /* FIXME: Are NULL teams allowed? */
+  if (pc->access_level != ALLOW_HACK || !game.info.is_edit_mode
+      || !pplayer || !pnation || !pteam || !pgov) {
+    return;
+  }
+
+  sz_strlcpy(pplayer->name, packet->name);
+  sz_strlcpy(pplayer->username, packet->username);
+
+  pplayer->nation = pnation;
+  pplayer->is_male = packet->is_male;
+  pplayer->team = pteam;
+
+  pplayer->economic.gold = MAX(packet->gold, 0);
+  pplayer->economic.tax = CLIP(0, packet->tax, 100);
+  pplayer->economic.science = CLIP(0, packet->science, 100 - packet->tax);
+  pplayer->economic.luxury = 100 - packet->tax - packet->science;
+
+  pplayer->government = pgov;
+  pplayer->target_government = get_government(packet->target_government);
+
+#if 0 /* FIXME: These values need to be checked */
+  pplayer->embassy = packet->embassy;
+  pplayer->gives_shared_vision = packet->gives_shared_vision;
+  pplayer->city_style = packet->city_style;
+  for (i = 0; i < MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS; i++) {
+    pplayer->ai.love[i] = packet->love[i];
+  }
+
+  for (i = 0; i < MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS; i++) {
+    pplayer->diplstates[i].type = packet->diplstates[i].type;
+    pplayer->diplstates[i].turns_left = packet->diplstates[i].turns_left;
+    pplayer->diplstates[i].contact_turns_left
+      = packet->diplstates[i].contact_turns_left;
+    pplayer->diplstates[i].has_reason_to_cancel
+      = packet->diplstates[i].has_reason_to_cancel;
+  }
+
+  for (i = 0; i < B_LAST/*game.num_impr_types*/; i++) {
+     pplayer->small_wonders[i] = packet->small_wonders[i];
+  }
+  
+  /* FIXME: What's an AI value doing being set here? */
+  pplayer->ai.science_cost = packet->science_cost;
+
+  pplayer->bulbs_last_turn = packet->bulbs_last_turn;
+
+  research = get_player_research(pplayer);
+  research->bulbs_researched = packet->bulbs_researched;
+  research->techs_researched = packet->techs_researched;
+  research->researching = packet->researching;
+  research->future_tech = packet->future_tech;
+  research->tech_goal = packet->tech_goal;
+
+  pplayer->is_alive = packet->is_alive;
+  pplayer->ai.barbarian_type = packet->barbarian_type;
+  pplayer->revolution_finishes = packet->revolution_finishes;
+  pplayer->ai.control = packet->ai;
+#endif
+
+  /* TODO: and probably a bunch more stuff here */
+
+  /* send update back to client */
+  send_player_info(NULL, pplayer);  
+}
Index: server/unittools.c
===================================================================
--- server/unittools.c  (revision 11583)
+++ server/unittools.c  (working copy)
@@ -2061,7 +2061,8 @@
   }
 
   /* Safe terrain according to player map? */
-  if (!is_native_terrain(punit, map_get_player_tile(ptile, pplayer)->terrain)) 
{
+  if (!is_native_terrain(punit->type,
+                         map_get_player_tile(ptile, pplayer)->terrain)) {
     notify_player(pplayer, ptile, E_BAD_COMMAND,
                      _("This unit cannot paradrop into %s."),
                        get_name(map_get_player_tile(ptile, pplayer)->terrain));
Index: server/Makefile.am
===================================================================
--- server/Makefile.am  (revision 11583)
+++ server/Makefile.am  (working copy)
@@ -39,6 +39,7 @@
                diplhand.h      \
                diplomats.c     \
                diplomats.h     \
+               edithand.c      \
                gamehand.c      \
                gamehand.h      \
                gotohand.c      \
Index: common/packets.def
===================================================================
--- common/packets.def  (revision 11583)
+++ common/packets.def  (working copy)
@@ -199,6 +199,8 @@
 type BV_ROLES          = bitvector(bv_roles)
 type BV_TERRAIN_FLAGS  = bitvector(bv_terrain_flags)
 type BV_CITY_OPTIONS    = bitvector(bv_city_options)
+type BV_SPECIAL         = bitvector(bv_special)
+type BV_PLAYER          = bitvector(bv_player)
 type DIPLSTATE         = diplstate(struct player_diplstate)
 type VISION            = uint32(unsigned int)
 
@@ -242,7 +244,7 @@
   Spaceship
   Ruleset
 
-The last used packet number is 117.
+The last used packet number is 129.
 ****************************************************/
 
 
@@ -357,6 +359,7 @@
   UINT8 aifill;
 
   BOOL is_new_game;   # TRUE only in pregame for "new" (not loaded) games
+  BOOL is_edit_mode;  # If set, editing is allowed
   FLOAT seconds_to_phasedone;
   UINT32 timeout;
   TURN turn;
@@ -1269,7 +1272,7 @@
 /*********************************************************
  Below are the packets that control single-player mode.  
 *********************************************************/
-PACKET_SINGLE_WANT_HACK_REQ=108;cs,handle-per-conn,no-handle
+PACKET_SINGLE_WANT_HACK_REQ=108;cs,handle-per-conn,handle-via-packet
  STRING token[MAX_LEN_NAME];
 end
 
@@ -1347,3 +1350,92 @@
   STRING graphic_str[MAX_LEN_NAME];
   STRING graphic_alt[MAX_LEN_NAME];
 end
+
+/************** Editing hash packets **********************/
+
+PACKET_EDIT_MODE=129;cs,handle-per-conn,dsend
+  BOOL state;
+end
+
+PACKET_EDIT_TILE=125;cs,handle-per-conn,dsend
+  COORD x, y; key
+  TERRAIN terrain;
+  RESOURCE resource;
+  BV_SPECIAL special;
+end
+
+PACKET_EDIT_UNIT=126;cs,handle-per-conn,lsend
+  UNIT id; key
+  BOOL create_new, delete;
+  PLAYER owner;
+  COORD x,y;
+  CITY homecity;
+
+  UINT8 veteran;
+  BOOL paradropped;
+
+  UNIT_TYPE type;
+  UNIT transported_by; /* Only valid if transported is set. */
+  UINT8 movesleft, hp, fuel, activity_count;
+
+  # Other values of the unit (done_moving, orders, etc.) can be set directly
+  # through regular packets.  Some values are simply caches and cannot
+  # be controlled even by a privilidged client.
+end
+
+PACKET_EDIT_CITY=127;cs,handle-per-conn,lsend
+  PLAYER owner;
+  COORD x, y; key # cities are keyed by tile not by ID
+  UINT8 size;
+  UINT16 food_stock, shield_stock;
+  UINT16 trade[NUM_TRADEROUTES];
+  TURN turn_last_built;
+  UINT8 changed_from_id;
+  BOOL changed_from_is_unit;
+  UINT16 before_change_shields;
+  UINT16 disbanded_shields;
+  UINT16 caravan_shields;
+  UINT16 last_turns_shield_surplus;
+  BV_IMPRS improvements;
+  BOOL did_buy, did_sell, was_happy, airlift, diplomat_investigate;
+  TURN turn_founded;
+
+  # Other values of the city (worker placement, worklist, etc.) can be set
+  # directly through regular packets.  Some values are simply caches and
+  # cannot be controlled even by a privilidged client.
+end
+
+PACKET_EDIT_PLAYER=128;cs,handle-per-conn,lsend
+  PLAYER playerno; key 
+  STRING name[MAX_LEN_NAME];
+  STRING username[MAX_LEN_NAME];
+  BOOL is_observer;
+  BOOL is_male;
+  GOVERNMENT government;
+  GOVERNMENT target_government;
+  BV_PLAYER embassy;
+  UINT8 city_style;
+  NATION nation;
+  TEAM team;
+  BOOL phase_done;
+  TURN nturns_idle;
+  BOOL is_alive;
+  DIPLSTATE diplstates[MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS];
+  GOLD gold;
+  PERCENT tax, science,luxury;
+  UINT16 bulbs_last_turn;
+  UINT32 bulbs_researched;
+  UINT32 techs_researched;
+  UINT8 researching;
+  UINT16 science_cost;
+  UINT16 future_tech;
+  UINT8 tech_goal;
+  BOOL is_connected;
+  TURN revolution_finishes;
+  BOOL ai;
+  UINT8 barbarian_type;
+  VISION gives_shared_vision;
+  BIT_STRING inventions[A_LAST+1];
+  SINT16 love[MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS];
+  UINT16 small_wonders[B_LAST]; diff
+end
Index: common/city.c
===================================================================
--- common/city.c       (revision 11583)
+++ common/city.c       (working copy)
@@ -729,10 +729,13 @@
                              city_x, city_y, is_celebrating, otype);
 }
 
-/**************************************************************************
+/****************************************************************************
   Returns TRUE if the given unit can build a city at the given map
-  coordinates.  punit is the founding unit.
-**************************************************************************/
+  coordinates.
+
+  punit is the founding unit.  It may be NULL if a city is built out of the
+  blue (e.g., through editing).
+****************************************************************************/
 bool city_can_be_built_here(const struct tile *ptile, const struct unit *punit)
 {
   int citymindist;
@@ -742,13 +745,13 @@
     return FALSE;
   }
 
-  if (!can_unit_exist_at_tile(punit, ptile)) {
+  if (punit && !can_unit_exist_at_tile(punit, ptile)) {
     /* We allow land units to build land cities and sea units to build
      * ocean cities. Air units can build cities anywhere. */
     return FALSE;
   }
 
-  if (ptile->owner && ptile->owner != punit->owner) {
+  if (punit && ptile->owner && ptile->owner != punit->owner) {
     /* Cannot steal borders by settling. This has to be settled by
      * force of arms. */
     return FALSE;
@@ -2376,7 +2379,15 @@
   /* Set up the worklist */
   init_worklist(&pcity->worklist);
 
-  {
+  if (!ptile) {
+    /* HACK: if a "dummy" city is created with no tile, the regular
+     * operations to choose a build target would fail.  This situation
+     * probably should be forbidden, but currently it might happen during
+     * map editing.  This fallback may also be dangerous if it gives an
+     * invalid production. */
+    pcity->production.is_unit = TRUE;
+    pcity->production.value = 0;
+  } else {
     struct unit_type *u = best_role_unit(pcity, L_FIRSTBUILD);
 
     if (u) {
Index: common/movement.c
===================================================================
--- common/movement.c   (revision 11583)
+++ common/movement.c   (working copy)
@@ -174,17 +174,17 @@
     return TRUE;
   }
 
-  return is_native_terrain(punit, ptile->terrain);
+  return is_native_terrain(punit->type, ptile->terrain);
 }
 
 /****************************************************************************
   This terrain is native to unit. Units that require fuel dont survive
   even on native terrain. All terrains are native to air units.
 ****************************************************************************/
-bool is_native_terrain(const struct unit *punit,
+bool is_native_terrain(const struct unit_type *punittype,
                        const struct terrain *pterrain)
 {
-  switch (punit->type->move_type) {
+  switch (punittype->move_type) {
   case LAND_MOVING:
     return !is_ocean(pterrain);
   case SEA_MOVING:
Index: common/movement.h
===================================================================
--- common/movement.h   (revision 11583)
+++ common/movement.h   (working copy)
@@ -30,7 +30,7 @@
 bool is_heli_unittype(const struct unit_type *punittype);
 bool is_ground_unittype(const struct unit_type *punittype);
 
-bool is_native_terrain(const struct unit *punit,
+bool is_native_terrain(const struct unit_type *punittype,
                        const struct terrain *pterrain);
 bool can_unit_exist_at_tile(const struct unit *punit, const struct tile 
*ptile);
 bool can_unit_survive_at_tile(const struct unit *punit,
Index: common/terrain.h
===================================================================
--- common/terrain.h    (revision 11583)
+++ common/terrain.h    (working copy)
@@ -41,6 +41,17 @@
 
 BV_DEFINE(bv_special, S_LAST);
 
+#define specials_iterate(special)                                          \
+{                                                                          \
+  enum tile_special_type special;                                          \
+                                                                           \
+  for (special = 0; special < S_LAST; special++) {
+
+#define specials_iterate_end                                               \
+  }                                                                        \
+}
+
+
 #define T_NONE (NULL) /* A special flag meaning no terrain type. */
 #define T_UNKNOWN (NULL) /* An unknown terrain. */
 
Index: common/game.c
===================================================================
--- common/game.c       (revision 11583)
+++ common/game.c       (working copy)
@@ -247,6 +247,7 @@
   game.info.government_when_anarchy_id = G_MAGIC;   /* flag */
 
   game.info.is_new_game   = TRUE;
+  game.info.is_edit_mode = FALSE;
   game.simultaneous_phases_stored = GAME_DEFAULT_SIMULTANEOUS_PHASES;
   game.timeoutint    = GAME_DEFAULT_TIMEOUTINT;
   game.timeoutintinc = GAME_DEFAULT_TIMEOUTINTINC;
Index: common/map.c
===================================================================
--- common/map.c        (revision 11583)
+++ common/map.c        (working copy)
@@ -623,7 +623,7 @@
 
   if (punit) {
     pclass = get_unit_class(punit->type);
-    native = is_native_terrain(punit, t2->terrain);
+    native = is_native_terrain(punit->type, t2->terrain);
   }
 
   if (game.info.slow_invasions
Index: client/control.c
===================================================================
--- client/control.c    (revision 11583)
+++ client/control.c    (working copy)
@@ -2712,3 +2712,11 @@
     set_unit_focus_and_select(punit2);
   } unit_list_iterate_end;
 }
+
+/**************************************************************************
+  Toggle editor mode in the server.
+**************************************************************************/
+void key_editor_toggle(void)
+{
+  dsend_packet_edit_mode(&aconnection, !game.info.is_edit_mode);
+}
Index: client/gui-gtk-2.0/menu.c
===================================================================
--- client/gui-gtk-2.0/menu.c   (revision 11583)
+++ client/gui-gtk-2.0/menu.c   (working copy)
@@ -40,6 +40,7 @@
 #include "connectdlg.h"
 #include "control.h"
 #include "dialogs.h"
+#include "editdlg.h"
 #include "finddlg.h"
 #include "gotodlg.h"
 #include "graphics.h"
@@ -158,6 +159,9 @@
   MENU_REPORT_MESSAGES,
   MENU_REPORT_DEMOGRAPHIC,
   MENU_REPORT_SPACESHIP,
+ 
+  MENU_EDITOR_TOGGLE,
+  MENU_EDITOR_TOOLS,
 
   MENU_HELP_LANGUAGES,
   MENU_HELP_CONNECTING,
@@ -580,6 +584,21 @@
   }
 }
 
+/****************************************************************************
+  Callback function for when an item is chosen from the "editor" menu.
+****************************************************************************/
+static void editor_menu_callback(gpointer callback_data,
+                                 guint callback_action, GtkWidget *widget)
+{   
+  switch(callback_action) {
+  case MENU_EDITOR_TOGGLE:
+    key_editor_toggle();
+    break;
+  case MENU_EDITOR_TOOLS:
+    editdlg_show_tools();
+    break;
+  }
+}
 
 /****************************************************************
 ...
@@ -905,6 +924,15 @@
        reports_menu_callback,  MENU_REPORT_DEMOGRAPHIC                         
        },
   { "/" N_("Reports") "/" N_("S_paceship"),            "F12",
        reports_menu_callback,  MENU_REPORT_SPACESHIP                           
        },
+
+  /* Editor menu */
+  { "/" N_("_Editor"), NULL, NULL, 0, "<Branch>" },
+  { "/" N_("_Editor") "/tearoff1", NULL, NULL, 0, "<Tearoff>" },
+  { "/" N_("_Editor") "/" N_("Editing Mode"), NULL,
+    editor_menu_callback, MENU_EDITOR_TOGGLE, "<CheckItem>" },
+  { "/" N_("_Editor") "/" N_("_Tools"), NULL,
+    editor_menu_callback, MENU_EDITOR_TOOLS },
+
   /* Help menu ... */
   { "/" N_("_Help"),                                   NULL,
        NULL,                   0,                                      
"<Branch>"      },
@@ -1210,6 +1238,7 @@
     menus_set_sensitive("<main>/_Government", FALSE);
     menus_set_sensitive("<main>/_View", FALSE);
     menus_set_sensitive("<main>/_Orders", FALSE);
+    menus_set_sensitive("<main>/_Editor", FALSE);
   } else {
     const char *path =
       menu_path_remove_uline("<main>/_Government/_Change Government");
@@ -1296,6 +1325,10 @@
 
     menus_set_active("<main>/_View/_Full Screen", fullscreen_mode);
 
+    menus_set_sensitive("<main>/_Editor", TRUE);
+    menus_set_sensitive("<main>/_Editor/_Tools", game.info.is_edit_mode);
+    menus_set_active("<main>/_Editor/Editing Mode", game.info.is_edit_mode);
+
     /* Remaining part of this function: Update Orders menu */
 
     if (!can_client_issue_orders()) {
Index: client/gui-gtk-2.0/editdlg.c
===================================================================
--- client/gui-gtk-2.0/editdlg.c        (revision 0)
+++ client/gui-gtk-2.0/editdlg.c        (revision 0)
@@ -0,0 +1,482 @@
+/********************************************************************** 
+ Freeciv - Copyright (C) 2005 - The Freeciv Project
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+***********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "fcintl.h"
+#include "shared.h"
+#include "support.h"
+
+#include "game.h"
+#include "government.h"
+#include "packets.h"
+
+#include "editor.h"
+
+#include "editdlg.h"
+#include "gui_main.h"
+#include "gui_stuff.h"
+
+#define TABLE_WIDTH 3
+#define TOOL_WIDTH 5
+
+typedef struct {
+  const char *name;
+  int paint;
+} paint_item;
+
+static paint_item *terrains;
+
+static paint_item specials[] = {
+  { N_("clear all"), S_LAST },
+  { NULL, S_ROAD },
+  { NULL, S_IRRIGATION },
+  { NULL, S_RAILROAD },
+  { NULL, S_MINE },
+  { NULL, S_POLLUTION },
+  { NULL, S_HUT },
+  { NULL, S_FORTRESS },
+  { NULL, S_RIVER },
+  { NULL, S_FARMLAND },
+  { NULL, S_AIRBASE },
+  { NULL, S_FALLOUT }
+};
+
+static char *tool_names[ETOOL_LAST] = {
+  N_("Paint"), N_("Unit"), N_("City"), N_("Player"), N_("Delete")
+};
+
+#define SPECIALS_NUM ARRAY_SIZE(specials)
+
+static GtkWidget *toolwin;
+static GtkWidget *notebook;
+
+static GList *tool_group;
+static GList *map_group;
+
+/*****************************************************************************
+ handle the toggle buttons' toggle events
+*****************************************************************************/
+static void tool_toggled(GtkWidget *widget, int tool)
+{
+  editor_set_selected_tool_type(tool);
+  /* switch pages if necessary */
+  gtk_notebook_set_page(GTK_NOTEBOOK(notebook), tool);
+}
+
+/****************************************************************************
+  callback for button that is in a group. we want to untoggle all the
+  other buttons in the group
+*****************************************************************************/
+static void toggle_group_callback(GtkWidget *w, gpointer data)
+{
+  int i;
+  GList *group = (GList *)data;
+
+  /* untoggle all the other buttons in the group toggle this one */
+  for (i = 0 ; i < g_list_length(group); i++) {
+    GtkWidget *button = (GtkWidget *)g_list_nth_data(group, i);
+    int id = (int)g_object_get_data(G_OBJECT(button), "sigid");
+
+    g_signal_handlers_block_by_func(button,
+                                    G_CALLBACK(toggle_group_callback), data);
+    g_signal_handler_block(button, id);
+    gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(button), (button == w));
+    g_signal_handlers_unblock_by_func(button,
+                                      G_CALLBACK(toggle_group_callback), data);
+    g_signal_handler_unblock(button, id);
+  }
+}
+
+/**************************************************************************
+ fill the tools[]-array with the missing values: pixmaps, buttons, ids
+***************************************************************************/
+static void create_tools(GtkWidget *win, GtkWidget *parent)
+{
+  GtkWidget *button, *table;
+  int i, sig;
+
+  table = gtk_table_new(TOOL_WIDTH, 2, FALSE);
+  gtk_box_pack_start(GTK_BOX(parent), table, FALSE, TRUE, 0);
+
+  for (i = 0; i < ETOOL_LAST; i++) {
+    button = gtk_toggle_button_new_with_label(tool_names[i]);
+
+    /* must do this here. we can't call tool_toggled on palette creation */
+    gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(button),
+                                (i == 0) ? TRUE : FALSE);
+
+    sig = g_signal_connect(button, "toggled",
+                           G_CALLBACK(tool_toggled), GINT_TO_POINTER(i));
+
+    tool_group = g_list_append(tool_group, button);
+
+    /* add this group and the signal id to widget internal data */
+    g_signal_connect(button, "toggled", G_CALLBACK(toggle_group_callback),
+                     (gpointer)tool_group);
+    g_object_set_data(G_OBJECT(button), "sigid", GINT_TO_POINTER(sig));
+
+    /* do the rest for both types of buttons */
+    gtk_table_attach(GTK_TABLE(table), button,
+                     i % TOOL_WIDTH, i % TOOL_WIDTH + 1, i / TOOL_WIDTH,
+                     i / TOOL_WIDTH + 1, GTK_FILL, GTK_FILL, 1, 1);
+
+  }
+
+  gtk_widget_show_all(table);
+}
+
+/****************************************************************************
+ select a paint type and depress the particular button
+*****************************************************************************/
+static void set_selected_paint(GtkWidget *w, gpointer data)
+{
+  int id = GPOINTER_TO_INT(data);
+  enum editor_paint_type paint
+    = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(w), "paint"));
+
+  editor_set_selected_paint_type(paint);
+
+  assert(paint >= 0 && paint < EPAINT_LAST);
+  switch(paint){
+  case EPAINT_TERRAIN:
+    editor_set_selected_terrain(get_terrain(terrains[id].paint));
+    break;
+  case EPAINT_SPECIAL:
+    editor_set_selected_special(specials[id].paint);
+    break;
+  case EPAINT_LAST:
+    break;
+  }
+}
+
+/******************************************************************
+ KILLOFF: this is for demonstration purposes only (and not demonstration of
+          coding goodness to be sure!)
+*******************************************************************/
+static void unit_callback(GtkSpinButton *spinbutton, gpointer data)
+{
+  struct unit *punit = editor_get_selected_unit();
+  int param = GPOINTER_TO_INT(data);
+
+  switch (param) {
+  case 0:
+    punit->owner = get_player(gtk_spin_button_get_value_as_int(spinbutton));
+    break;
+  case 1:
+    punit->type = get_unit_type(gtk_spin_button_get_value_as_int(spinbutton));
+    break;
+  case 2:
+    punit->moves_left = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  case 3:
+    punit->activity = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  case 4:
+    punit->activity_target = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  case 5:
+    punit->activity_count = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  }
+}
+
+/******************************************************************
+ KILLOFF: this is for demonstration purposes only (and not demonstration of
+          coding goodness to be sure!)
+*******************************************************************/
+static void city_callback(GtkSpinButton *spinbutton, gpointer data)
+{
+  struct city *pcity = editor_get_selected_city();
+  int param = GPOINTER_TO_INT(data);
+
+  switch (param) {
+  case 0:
+    pcity->owner = get_player(gtk_spin_button_get_value_as_int(spinbutton));
+    break;
+  case 1:
+    pcity->size = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  case 2:
+    pcity->food_stock = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  case 3:
+    pcity->shield_stock = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  case 4:
+    pcity->pollution = gtk_spin_button_get_value_as_int(spinbutton);
+    break;
+  }
+}
+
+#if 0
+/******************************************************************
+ KILLOFF: this is for demonstration purposes only (and not demonstration of
+          coding goodness to be sure!)
+*******************************************************************/
+static void player_callback(GtkSpinButton *spinbutton, gpointer data)
+{
+}
+#endif
+
+/******************************************************************
+...
+*******************************************************************/
+static GtkWidget *create_map_palette(void)
+{
+  GtkWidget *button, *vbox;
+  GtkWidget *table = gtk_table_new(12, TABLE_WIDTH, TRUE);
+  int i, j, sig; 
+  int magic[3] = { 0, 5, 11 }; /* magic numbers to make the table look good */
+  int types_num[] = { game.control.terrain_count, SPECIALS_NUM };
+  paint_item *ptype[EPAINT_LAST] = { NULL, specials };
+  
+  terrains = fc_realloc(terrains,
+                       game.control.terrain_count * sizeof(*terrains));
+  ptype[0] = terrains;
+  
+  vbox = gtk_vbox_new(TRUE, 5);
+  
+  for(i = 0; i < EPAINT_LAST; i++) {
+    for(j = 0; j < types_num[i]; j++) {
+      paint_item *item = &ptype[i][j];
+
+      switch(i) {
+      case EPAINT_TERRAIN:
+        item->paint = j;
+        item->name = get_terrain(item->paint)->name;
+        break;
+      case EPAINT_SPECIAL:
+        if (!item->name) {
+         item->name = get_special_name(item->paint);
+       }
+        break;
+      }
+
+      button = gtk_toggle_button_new_with_label(item->name);
+
+      gtk_table_attach(GTK_TABLE(table), button,
+                       j % TABLE_WIDTH, j % TABLE_WIDTH + 1,
+                       j / TABLE_WIDTH + magic[i],  
+                       j / TABLE_WIDTH + magic[i] + 1, 
+                       GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
+
+      gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(button),
+                                  (i == 0 && j == 0) ? TRUE : FALSE);
+      sig = g_signal_connect(button, "toggled",
+                           G_CALLBACK(set_selected_paint), GINT_TO_POINTER(j));
+      gtk_object_set_data(GTK_OBJECT(button), "paint", GINT_TO_POINTER(i));
+
+      /* add this button to a group */
+      map_group = g_list_append(map_group, button);
+
+      /* add this group and the signal id to widget internal data */
+      g_signal_connect(button, "toggled", G_CALLBACK(toggle_group_callback),
+                       (gpointer)map_group);
+      g_object_set_data(G_OBJECT(button), "sigid", GINT_TO_POINTER(sig));
+    }
+  }
+  
+  editor_set_selected_terrain(get_terrain(terrains[0].paint));
+  gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 5);
+  gtk_widget_show_all(vbox);
+  
+  return vbox;
+}
+
+/******************************************************************
+ ...
+*******************************************************************/
+static GtkWidget *create_units_palette(void)
+{
+#define NUM_PARAMS 6
+
+  GtkWidget *hbox, *vbox, *label, *sb;
+  GtkAdjustment *adj;
+  int i;
+  struct unit *punit = editor_get_selected_unit();
+
+  const char *names[NUM_PARAMS] = { _("Owner"),
+                                   _("Type"),
+                                    _("Moves Left"),
+                                   _("Activity"),
+                                    _("Activity Target"),
+                                   _("Activity Count") };
+  int inits[NUM_PARAMS][3] = {
+    {punit->owner->player_no, 0, game.info.nplayers - 1},
+    {punit->type->index, 0, game.control.num_unit_types - 1},
+    {punit->moves_left, 0, 200},
+    {punit->activity, 0, ACTIVITY_LAST},
+    {punit->activity_target, 0, S_LAST},
+    {punit->activity_count, 0, 200}
+  };
+
+  vbox = gtk_vbox_new(FALSE, 5);
+
+  for (i = 0; i < NUM_PARAMS; i++) {
+    adj = (GtkAdjustment *)gtk_adjustment_new(inits[i][0], inits[i][1], 
+                                              inits[i][2], 1.0, 5.0, 5.0);
+    hbox = gtk_hbox_new(FALSE, 5);
+    sb = gtk_spin_button_new(adj, 1, 0);
+    label = gtk_label_new(names[i]);
+    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(hbox), sb, TRUE, TRUE, 0);
+
+    g_signal_connect(sb, "value-changed", G_CALLBACK(unit_callback),
+                     GINT_TO_POINTER(i));
+  }
+
+  return vbox;
+#undef NUM_PARAMS
+}
+
+/******************************************************************
+ ...
+*******************************************************************/
+static GtkWidget *create_city_palette(void)
+{
+#define NUM_PARAMS 5
+
+  GtkWidget *hbox, *vbox, *label, *sb;
+  GtkAdjustment *adj;
+  int i;
+  struct city *pcity = editor_get_selected_city();
+
+  const char *names[NUM_PARAMS] = { _("Owner"), _("Size"),
+                                    _("Food"), _("Shields"),
+                                    _("Pollution") };
+  int inits[NUM_PARAMS][3] = {
+    {pcity->owner->player_no, 0, game.info.nplayers - 1},
+    {pcity->size, 1, 63},
+    {pcity->food_stock, 0, 10000},
+    {pcity->shield_stock, 0, 10000},
+    {pcity->pollution, 0, 2000}
+  };
+
+  vbox = gtk_vbox_new(FALSE, 5);
+
+  for (i = 0; i < NUM_PARAMS; i++) {
+    adj = (GtkAdjustment *)gtk_adjustment_new(inits[i][0], inits[i][1], 
+                                              inits[i][2], 1.0, 5.0, 5.0);
+    hbox = gtk_hbox_new(FALSE, 5);
+    sb = gtk_spin_button_new(adj, 1, 0);
+    label = gtk_label_new(names[i]);
+    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(hbox), sb, TRUE, TRUE, 0);
+
+    g_signal_connect(sb, "value-changed", G_CALLBACK(city_callback),
+                     GINT_TO_POINTER(i));
+  }
+
+  return vbox;
+#undef NUM_PARAMS
+}
+
+/******************************************************************
+ ...
+*******************************************************************/
+static GtkWidget *create_player_palette(void)
+{
+  GtkWidget *vbox;
+
+  vbox = gtk_vbox_new(FALSE, 5);
+
+  return vbox;
+}
+
+/******************************************************************
+  Create the tools dialog from scratch.
+
+  FIXME: This absolutely has to be called anew each time we connect to a
+  new server, since the ruleset may be different and data needs to be reset.
+*******************************************************************/
+static void create_toolsdlg(void)
+{
+  GtkWidget *palette, *vbox;
+
+  if (toolwin) {
+    return;
+  }
+
+  editor_init_tools();
+
+  toolwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  setup_dialog(toolwin, toplevel);
+  gtk_window_set_title(GTK_WINDOW(toolwin), _("Editing Tools"));
+  gtk_container_set_border_width(GTK_CONTAINER(toolwin), 5);
+  gtk_window_set_policy(GTK_WINDOW(toolwin), FALSE, FALSE, FALSE);
+  g_signal_connect(toolwin, "delete_event",
+                  G_CALLBACK(editdlg_hide_tools), NULL);
+  g_signal_connect(toolwin, "destroy",
+                  GTK_SIGNAL_FUNC(gtk_widget_destroyed),
+                  &toolwin);
+
+  vbox = gtk_vbox_new(FALSE, 0);
+  gtk_container_add(GTK_CONTAINER(toolwin), vbox);
+
+  create_tools(toolwin, vbox);
+  notebook = gtk_notebook_new(); /* apparently, it must be here: set_page... */
+  gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 2);
+
+  gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
+  gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
+  gtk_box_pack_start(GTK_BOX(vbox), notebook, FALSE, FALSE, 0);
+
+  palette = create_map_palette();
+  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), palette, NULL);
+
+  palette = create_units_palette();
+  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), palette, NULL);
+
+  palette = create_city_palette();
+  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), palette, NULL);
+
+  palette = create_player_palette();
+  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), palette, NULL);
+
+  gtk_widget_show_all(toolwin);
+}
+
+/****************************************************************************
+  show the editor toolbox window
+****************************************************************************/
+void editdlg_show_tools(void)
+{
+  if (!toolwin) {
+    create_toolsdlg();
+  }
+  gtk_widget_show(toolwin);
+}
+
+/****************************************************************************
+  hide the editor toolbox window
+****************************************************************************/
+void editdlg_hide_tools(void)
+{
+  if (toolwin) {
+    gtk_widget_hide(toolwin);
+  }
+}
Index: client/gui-gtk-2.0/editdlg.h
===================================================================
--- client/gui-gtk-2.0/editdlg.h        (revision 0)
+++ client/gui-gtk-2.0/editdlg.h        (revision 0)
@@ -0,0 +1,23 @@
+/********************************************************************** 
+ Freeciv - Copyright (C) 2005 - The Freeciv Project
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+***********************************************************************/
+#ifndef FC__TOOLSDLG_H
+#define FC__TOOLSDLG_H
+
+#include <gtk/gtk.h>
+
+#include "shared.h"
+
+void editdlg_show_tools(void);
+void editdlg_hide_tools(void);
+
+#endif  /* FC__TOOLSDLG_H */
Index: client/gui-gtk-2.0/dialogs.c
===================================================================
--- client/gui-gtk-2.0/dialogs.c        (revision 11583)
+++ client/gui-gtk-2.0/dialogs.c        (working copy)
@@ -54,6 +54,7 @@
 #include "tilespec.h"
 
 #include "dialogs.h"
+#include "editdlg.h"
 #include "wldlg.h"
 
 /******************************************************************/
@@ -1265,5 +1266,6 @@
 void popdown_all_game_dialogs(void)
 {
   gui_dialog_destroy_all();
+  editdlg_hide_tools();
 }
 
Index: client/gui-gtk-2.0/Makefile.am
===================================================================
--- client/gui-gtk-2.0/Makefile.am      (revision 11583)
+++ client/gui-gtk-2.0/Makefile.am      (working copy)
@@ -43,6 +43,8 @@
        dialogs.h       \
        diplodlg.c      \
        diplodlg.h      \
+       editdlg.c       \
+       editdlg.h       \
        diplomat_dialog.c \
        finddlg.c       \
        finddlg.h       \
Index: client/control.h
===================================================================
--- client/control.h    (revision 11583)
+++ client/control.h    (working copy)
@@ -196,6 +196,8 @@
 void key_unit_assign_battlegroup(int battlegroup, bool append);
 void key_unit_select_battlegroup(int battlegroup, bool append);
 
+void key_editor_toggle(void);
+
 /* don't change this unless you also put more entries in data/Freeciv */
 #define MAX_NUM_UNITS_BELOW 4
 
Index: client/packhand.c
===================================================================
--- client/packhand.c   (revision 11583)
+++ client/packhand.c   (working copy)
@@ -1397,6 +1397,7 @@
     boot_help_texts();         /* reboot, after setting game.spacerace */
   }
   update_unit_focus();
+  update_menus();
   if (update_aifill_button) {
     update_start_page();
   }
Index: client/mapctrl_common.c
===================================================================
--- client/mapctrl_common.c     (revision 11583)
+++ client/mapctrl_common.c     (working copy)
@@ -33,16 +33,16 @@
 #include "clinet.h"
 #include "cma_core.h"
 #include "control.h"
+#include "editor.h"
 #include "fcintl.h"
 #include "goto.h"
+#include "mapctrl_common.h"
 #include "mapctrl_g.h"
 #include "mapview_g.h"
 #include "options.h"
 #include "overview_common.h"
 #include "tilespec.h"
 
-#include "mapctrl_common.h"
-
 /* Selection Rectangle */
 static int rec_anchor_x, rec_anchor_y;  /* canvas coordinates for anchor */
 static struct tile *rec_canvas_center_tile;
@@ -531,7 +531,9 @@
 {
   struct tile *ptile = canvas_pos_to_tile(canvas_x, canvas_y);
 
-  if (can_client_change_view() && ptile) {
+  if (game.info.is_edit_mode) {
+    editor_do_click(ptile);
+  } else if (can_client_change_view() && ptile) {
     /* FIXME: Some actions here will need to check can_client_issue_orders.
      * But all we can check is the lowest common requirement. */
     do_map_click(ptile, qtype);
Index: client/editor.c
===================================================================
--- client/editor.c     (revision 0)
+++ client/editor.c     (revision 0)
@@ -0,0 +1,272 @@
+/********************************************************************** 
+ Freeciv - Copyright (C) 2005 - The Freeciv Poject
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+***********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "log.h"
+#include "support.h"
+
+#include "game.h"
+#include "map.h"
+#include "packets.h"
+
+#include "climap.h"
+#include "clinet.h"
+#include "control.h"
+#include "editor.h"
+
+/* where the selected terrain and specials for editing are stored */
+static enum editor_tool_type selected_tool = ETOOL_PAINT;
+static enum tile_special_type selected_special = S_LAST;
+static struct terrain *selected_terrain = NULL;
+static enum editor_paint_type selected_paint_type = EPAINT_LAST;
+static struct unit *selected_unit;
+static struct city *selected_city;
+
+/****************************************************************************
+  Initialize the editor tools data.
+
+  FIXME: This absolutely has to be called anew each time we connect to a
+  new server, since the ruleset may be different and data needs to be reset.
+****************************************************************************/
+void editor_init_tools(void)
+{ 
+  struct player *pplayer = game.player_ptr ? game.player_ptr : get_player(0);
+
+  if (selected_unit) {
+    destroy_unit_virtual(selected_unit);
+  }
+  selected_unit = create_unit_virtual(pplayer, 0, get_unit_type(0), 0);
+
+  if (selected_city) {
+    remove_city_virtual(selected_city);
+  }
+  selected_city = create_city_virtual(pplayer, NULL, "");
+
+  selected_tool = ETOOL_PAINT;
+  selected_special = S_LAST;
+  selected_paint_type = EPAINT_LAST;
+  selected_terrain = NULL;
+}
+
+/****************************************************************************
+  Returns the currently selected editor tool type.
+****************************************************************************/
+void editor_set_selected_tool_type(enum editor_tool_type type)
+{
+  selected_tool = type;
+}
+
+/****************************************************************************
+  Returns the currently selected editor paint type.
+****************************************************************************/
+void editor_set_selected_paint_type(enum editor_paint_type type)
+{
+  selected_paint_type = type;
+}
+
+/****************************************************************************
+  Sets the selected editor terrain.
+****************************************************************************/
+void editor_set_selected_terrain(struct terrain *pterrain)
+{
+  selected_terrain = pterrain;
+}
+
+/****************************************************************************
+  Sets the selected editor special.
+****************************************************************************/
+void editor_set_selected_special(enum tile_special_type special)
+{
+  selected_special = special;
+}
+
+/****************************************************************************
+  Returns the selected unit.
+****************************************************************************/
+struct unit *editor_get_selected_unit(void)
+{
+  return selected_unit;
+}
+
+/****************************************************************************
+  Returns the selected city.
+****************************************************************************/
+struct city *editor_get_selected_city(void)
+{
+  return selected_city;
+}
+
+/****************************************************************************
+ problem: could be multiple units on a particular tile
+ TODO: edit existing units
+****************************************************************************/
+static void do_unit(struct tile *ptile)
+{
+  struct packet_edit_unit packet;
+
+  packet.create_new = TRUE; /* ? */
+  packet.delete = FALSE;
+
+  packet.id = selected_unit->id;
+  packet.owner = selected_unit->owner->player_no;
+
+  packet.x = ptile->x;
+  packet.y = ptile->y;
+
+  packet.homecity = selected_unit->homecity;
+
+  packet.veteran = selected_unit->veteran;
+  packet.paradropped = selected_unit->paradropped;
+
+  packet.type = selected_unit->type->index;
+  packet.transported_by = selected_unit->transported_by;
+
+  packet.movesleft = selected_unit->moves_left;
+  packet.hp = selected_unit->hp;
+  packet.fuel = selected_unit->fuel;
+
+  packet.activity_count = selected_unit->activity_count;
+
+  send_packet_edit_unit(&aconnection, &packet);
+}
+
+/****************************************************************************
+ basically package_city in citytools.c
+****************************************************************************/
+static void do_city(struct tile *ptile)
+{
+  struct packet_edit_city packet;
+  struct city *pcity = selected_city;
+  int i;
+
+  packet.owner = pcity->owner->player_no;
+  packet.x = ptile->x;
+  packet.y = ptile->y;
+
+  packet.size = pcity->size;
+  for (i = 0; i < NUM_TRADEROUTES; i++) {
+    packet.trade[i] = pcity->trade[i];
+  }
+
+  packet.food_stock = pcity->food_stock;
+  packet.shield_stock = pcity->shield_stock;
+
+  packet.turn_last_built = pcity->turn_last_built;
+  packet.turn_founded = pcity->turn_founded;
+  packet.changed_from_is_unit = pcity->changed_from.is_unit;
+  packet.changed_from_id = pcity->changed_from.value;
+  packet.before_change_shields = pcity->before_change_shields;
+  packet.disbanded_shields = pcity->disbanded_shields;
+  packet.caravan_shields = pcity->caravan_shields;
+  packet.last_turns_shield_surplus = pcity->last_turns_shield_surplus;
+
+  packet.diplomat_investigate = FALSE; /* FIXME: this overwrites the value! */
+
+  packet.airlift = pcity->airlift;
+  packet.did_buy = pcity->did_buy;
+  packet.did_sell = pcity->did_sell;
+  packet.was_happy = pcity->was_happy;
+
+  BV_CLR_ALL(packet.improvements);
+  impr_type_iterate(building) {
+    if (city_got_building(pcity, building)) {
+      BV_SET(packet.improvements, building);
+    }
+  } impr_type_iterate_end;
+
+  send_packet_edit_city(&aconnection, &packet);
+}
+
+#if 0
+/****************************************************************************
+ basically package_city in citytools.c
+****************************************************************************/
+void do_edit_player(void)
+{
+  struct packet_edit_player packet;
+
+  send_packet_edit_city(&aconnection, &packet);
+}
+#endif
+
+/****************************************************************************
+  Does the current paint operation onto the tile.
+
+  For instance, if the paint operation is paint-terrain, then we just change
+  the current tile's terrain to the selected terrain.
+****************************************************************************/
+static void do_paint(struct tile *ptile)
+{
+  struct tile tile = *ptile;
+
+  switch (selected_paint_type) {
+  case EPAINT_TERRAIN:
+    if (selected_terrain) {
+      tile.terrain = selected_terrain;
+    }
+    break;
+  case EPAINT_SPECIAL:
+    /* add new special to existing specials on the tile */
+    if (selected_special == S_LAST) {
+      tile_clear_all_specials(&tile);
+    } else {
+      tile_add_special(&tile, selected_special);
+    }
+    break;
+  case EPAINT_LAST:
+    return;
+  } 
+
+  /* send the result to the server for changing */
+  /* FIXME: No way to change resources. */
+  dsend_packet_edit_tile(&aconnection, ptile->x, ptile->y,
+                        tile.terrain->index,
+                        tile.resource ? tile.resource->index : -1,
+                        tile.special);
+}
+
+/****************************************************************************
+  if the client is in edit_mode, then this function captures clicks on the
+  map canvas.
+****************************************************************************/
+void editor_do_click(struct tile *ptile)
+{
+  /* Editing tiles that we can't see (or are fogged) will only lead to
+   * problems. */
+  if (client_tile_get_known(ptile) != TILE_KNOWN) {
+    return;
+  }
+
+  switch (selected_tool) {
+  case ETOOL_PAINT:
+    do_paint(ptile);
+    break;
+  case ETOOL_UNIT:
+    do_unit(ptile);
+    break;
+  case ETOOL_CITY:
+    do_city(ptile);
+    break;
+  case ETOOL_PLAYER:
+  case ETOOL_DELETE:
+  case ETOOL_LAST:
+    break;
+  }
+}
Index: client/Makefile.am
===================================================================
--- client/Makefile.am  (revision 11583)
+++ client/Makefile.am  (working copy)
@@ -145,6 +145,8 @@
        colors_common.h         \
        control.c       \
        control.h       \
+       editor.c                \
+       editor.h                \
        ggzclient.c     \
        ggzclient.h     \
        goto.c          \
Index: client/editor.h
===================================================================
--- client/editor.h     (revision 0)
+++ client/editor.h     (revision 0)
@@ -0,0 +1,49 @@
+/**********************************************************************
+ Freeciv - Copyright (C) 2005 - The Freeciv Poject
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+***********************************************************************/
+
+#ifndef FC__TOOLS_H
+#define FC__TOOLS_H
+
+#include "fc_types.h"
+
+enum editor_tool_type {
+  ETOOL_PAINT,
+  ETOOL_UNIT,
+  ETOOL_CITY,
+  ETOOL_PLAYER,
+  ETOOL_DELETE,
+  ETOOL_LAST
+};
+
+enum editor_paint_type {
+  EPAINT_TERRAIN,
+  EPAINT_SPECIAL,
+  EPAINT_LAST
+};
+
+typedef void (*ToolFunction)(struct tile *ptile);
+
+void editor_init_tools(void);
+void editor_show_tools(void);
+
+void editor_set_selected_tool_type(enum editor_tool_type type);
+void editor_set_selected_paint_type(enum editor_paint_type type);
+void editor_set_selected_terrain(struct terrain *pterrain);
+void editor_set_selected_special(enum tile_special_type special);
+
+struct unit *editor_get_selected_unit(void);
+struct city *editor_get_selected_city(void);
+
+void editor_do_click(struct tile *ptile);
+
+#endif /* FC__TOOLS_H */

[Prev in Thread] Current Thread [Next in Thread]