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

Here is a small doc and a patch to trunk r13023. It is ready for
comment or commit if wanted.
On my wishlist is to get the startyear patch into the 2.1 branch

Ulrik

# Using Effects for the Calendar #
For default ruleset and flexibility for mods and the future.

## Change summary ##

Add `startyear` server option
Add `MinYear` requirement type "year greater or equal"
Add `Slow_Down_Timeline` effect to slow down normal calender
  the old slow down renamed to `Slow_Down_Timeline_2`
Modify `game_next_year` in game.c
Update default ruleset and documentation
Update cityturn.c and helpdata.c

## Motivation ##

Use game.info.turn (int) to drive the game
Use game.info.year (int) to drive the story

Continuing use of .year
We have to use .year in savegames and official representation to make
the saves and game behaviour *memoryless*: no "replays" neccessary for
reloading a save or getting back to the same point.


Effects
`Slow_Down_Timeline` and `Slow_Down_Timeline_2` where `_2` takes
precedence and represents the current space module slowdown (only
active when spacerace is 1), and the `Slow_Down_Timeline` is (by
default) an emulation of the old calendar system, numbering:
  value   yrs/turn
  0       50
  1       25
  2       20
  3       10
  4       5
  5       2
  >=6       1

_`game.info.year` should be strictly monotonous as a function of .turn_
Also, the quirk that year has to be > 0 for the 1 yr/turn to apply has
been removed.

Requirement type
Introducing the `MinYear` requirement type. Range must always be
World. Can also be converted to a less than test with negation and
combinations yield an equality test.
`MinYear` is thought to only operate on the current game clock. It
does not support testing for effects at a certain year the future.
Anyway, the thought is that a `MinYear` effect is the best when the
calendar is changing. Special years make more sense to civlization
history than special turns.


## Sideffects and unwanted quirks ##
The timeline is now unpredictable, even more so that previously. This
should pose no problem theoretically as the space modules code has
always been there so code cannot simply use "years" to look ahead in
time with.
The new effects code adds the possibility of any kinds of effects that
are activated by year ranges.

Adding Year as a requirement qualifier means that we commit ourselves
to this view of the game, that every turn should have a year assigned
and so on. However, this can still (theoretically) be an int between
+/-32000, so it is possible to run a game during 3000 to 5000 AD or
from 2200 to 500 BC, with deaccelerating timelines.

Also, the startyear and the calendar slowdowns go together. With turn
configuration, startyear would only be an offset. Now, startyear puts
you right there in relation to the timeline; if you start at year 1
AD, the calendar doesn't run at 50 years/turn from there.

Some rounding could be needed (and neccessary if we go down from say 6
to 1 or 0), so that 50 yrs/turn will be rounded to nearest 10 years;
and 25, 20 and 10 to the nearest 5. This rounding should always be
made in the positive direction so no loops can occur.

## Design Desicions ##
Use `Slow_Down_Timeline` or `Slow_Down_Timeline_2` for space
slowdowns? Using the former s good for legacy. Using the latter is
good in that it is a clearer mark that the space slowdown is a quirk.

Should rounding be used for year increments? It complicates the code,
but is useful in situations where the ruleset wants a non-year
triggered timeline change to *more* years per turn.

Is `MinYear` a good name?


## Further work ##
Futher things that could be worked on

* Patches for civ1 and civ2 ruleset coming soon
* Incorporate minor changes to 2.1 branch, like `startyear` and/or any
renamed effects (like `Slow_Down_Timeline_2`) to
* Clean up .year usage, make client only use strings for the date.

## Example ##

Make Upkeep extra expensive during [-1000,500) with [ inclusive, exclusive).
Works!
    [effect_example]
    name    = "Upkeep_Factor"
    value   = 1
    reqs    =
        { "type", "name", "range"
          "MinYear", "-1000", "World"
        }
    nreqs   =
        { "type", "name", "range"
          "MinYear", "-500", "World"
        }

## Calendar effects ##
This is how a calendar effects would look, one per speedup

    [effect_calendar_1]
    name    = "Slow_Down_Timeline"
    value   = 1
    reqs    =
        { "type", "name", "range"
          "MinYear", "-1000", "World"
        }

Index: doc/README.effects
===================================================================
--- doc/README.effects	(revision 13023)
+++ doc/README.effects	(arbetskopia)
@@ -40,9 +40,9 @@
 
 A requirement type is the type of the requirement and can be one of "None" 
 (default), "Tech", "Gov", "Building", "Special", "Terrain", "UnitType", 
-"UnitFlag", "UnitClass", "Nation", "OutputType", "MinSize", "AI" and
-"TerrainClass". MinSize is the minimum size of a city required. AI is ai
-player difficulty level. TerrainClass is either "Land" or "Oceanic".
+"UnitFlag", "UnitClass", "Nation", "OutputType", "MinSize", "AI",
+"TerrainClass" and "MinYear". MinSize is the minimum size of a city required. AI is ai
+player difficulty level. TerrainClass is either "Land" or "Oceanic". MinYear is a qualifier for the game year (Year greater than or equal the given year)
 
 Effect types 
 ============
@@ -207,10 +207,23 @@
     Gain amount points of "AI love" with AI(s).
 
 Slow_Down_Timeline
-    Slow down the timeline based on the AMOUNT. If AMOUNT >= 3 the timeline 
-will be 1 year/turn; with AMOUNT == 2 it is 2 years/turn; with AMOUNT == 1 it 
-is 5 years/turn; with AMOUNT <= 0 the timeline is unaffected. The effect will 
-be ignored if game.spacerace isn't set.
+    Slow down the timeline based on the amount. If amount >= 6 the timeline 
+will be 1 year/turn; with value <= 0 the timeline is unaffected.
+    amount    years/turn
+    >= 6      1
+    5         2
+    4         5
+    3         10
+    2         20
+    1         25
+    default   50
+    
+Slow_Down_Timeline_2
+    Override effect for Space Modules to slow down the timeline based on the amount. This overrides Slow_Down_Timeline.
+    If amount >= 3 the timeline 
+will be 1 year/turn; with amount == 2 it is 2 years/turn; with amount == 1 it 
+is 5 years/turn; with amount <= 0 it has no effect. 
+    This effect will be ignored if game.spacerace isn't set.
 
 Civil_War_Chance
     Chance of player splitting due to civil war when capital captured is 
Index: server/settings.c
===================================================================
--- server/settings.c	(revision 13023)
+++ server/settings.c	(arbetskopia)
@@ -162,6 +162,25 @@
 }
 
 /*************************************************************************
+  Verify that a given startyear is valid.
+*************************************************************************/
+static bool startyear_callback(int value, const char **error_string)
+{
+  if (value > game.info.end_year) {
+    /* Tried to set startyear later than endyear */
+    *error_string = _("Cannot set startyear later than endyear.");
+    return FALSE;
+  }
+  if (value == 0) {
+    /* There is no year 0 in the game
+     * FIXME: automatically set it to 1 AD */
+    *error_string = _("Year 0 is not a valid year.");
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/*************************************************************************
   Verify that a given maxplayers string is valid.
 *************************************************************************/
 static bool maxplayers_callback(int value, const char **error_string)
@@ -483,6 +502,15 @@
              "the ruleset, a big value for techlevel can make the next "
              "techs really expensive."), NULL,
 	  GAME_MIN_TECHLEVEL, GAME_MAX_TECHLEVEL, GAME_DEFAULT_TECHLEVEL)
+	  
+  GEN_INT("startyear", game.info.year,
+	  SSET_GAME_INIT, SSET_SOCIOLOGY, SSET_VITAL, SSET_TO_CLIENT,
+	  N_("Year the game begins"),
+	  N_("The game will begin at the given year. "
+	     "A negative number specifies years BC. Year 0 is not a valid "
+	     "year, use year 1 AD."),
+	  startyear_callback,
+	  GAME_MIN_START_YEAR, GAME_MAX_START_YEAR, GAME_DEFAULT_START_YEAR)
 
   GEN_INT("sciencebox", game.info.sciencebox,
 	  SSET_RULES, SSET_SCIENCE, SSET_SITUATIONAL, SSET_TO_CLIENT,
Index: server/report.c
===================================================================
--- server/report.c	(revision 13023)
+++ server/report.c	(arbetskopia)
@@ -936,7 +936,7 @@
   }
 
   if (!fp) {
-    if (game.info.year == GAME_START_YEAR) {
+    if (game.info.turn == 0) {
       oper = SL_CREATE;
     } else {
       fp = fopen(logname, "r");
Index: server/cityturn.c
===================================================================
--- server/cityturn.c	(revision 13023)
+++ server/cityturn.c	(arbetskopia)
@@ -883,6 +883,20 @@
 				 API_TYPE_CITY, pcity,
 				 API_TYPE_STRING, "need_terrainclass");
 	      break;
+	    case REQ_MINYEAR:
+	      /* FIXME: if negated: we should skip rather than postpone,
+	       * since we'll never be able to meet this req... */
+	      notify_player(pplayer, pcity->tile, E_CITY_CANTBUILD,
+			       _("%s can't build %s from the worklist; "
+				 "not available before year %d.  Postponing..."),
+			       pcity->name,
+			       get_impr_name_ex(pcity, building->index),
+			       preq->source.value.minyear);
+	      script_signal_emit("building_cant_be_built", 3,
+				 API_TYPE_BUILDING_TYPE, building,
+				 API_TYPE_CITY, pcity,
+				 API_TYPE_STRING, "need_minyear");
+	      break;
 	    case REQ_NONE:
 	    case REQ_LAST:
 	      assert(0);
Index: server/maphand.c
===================================================================
--- server/maphand.c	(revision 13023)
+++ server/maphand.c	(arbetskopia)
@@ -973,7 +973,7 @@
     }
   }
 
-  plrtile->last_updated = GAME_START_YEAR;
+  plrtile->last_updated = GAME_MIN_YEAR;
   vision_layer_iterate(v) {
     plrtile->own_seen[v] = plrtile->seen_count[v];
   } vision_layer_iterate_end;
Index: data/default/effects.ruleset
===================================================================
--- data/default/effects.ruleset	(revision 13023)
+++ data/default/effects.ruleset	(arbetskopia)
@@ -19,6 +19,54 @@
 ; Cheating AI effects are in separate file
 *include "default/ai_effects.ruleset"
 
+[effect_calendar_1]
+name    = "Slow_Down_Timeline"
+value   = 1
+reqs    =
+    { "type", "name", "range"
+      "MinYear", "-1000", "World"
+    }
+
+[effect_calendar_2]
+name    = "Slow_Down_Timeline"
+value   = 1
+reqs    =
+    { "type", "name", "range"
+      "MinYear", "0", "World"
+    }
+
+[effect_calendar_3]
+name    = "Slow_Down_Timeline"
+value   = 1
+reqs    =
+    { "type", "name", "range"
+      "MinYear", "1000", "World"
+    }
+
+[effect_calendar_4]
+name    = "Slow_Down_Timeline"
+value   = 1
+reqs    =
+    { "type", "name", "range"
+      "MinYear", "1500", "World"
+    }
+
+[effect_calendar_5]
+name    = "Slow_Down_Timeline"
+value   = 1
+reqs    =
+    { "type", "name", "range"
+      "MinYear", "1750", "World"
+    }
+    
+[effect_calendar_6]
+name    = "Slow_Down_Timeline"
+value   = 1
+reqs    =
+    { "type", "name", "range"
+      "MinYear", "1900", "World"
+    }
+
 [effect_unhappysize]
 name    = "City_Unhappy_Size"
 value   = 4
@@ -1453,7 +1501,7 @@
     }
 
 [effect_plastics_slowdown]
-name    = "Slow_Down_Timeline"
+name    = "Slow_Down_Timeline_2"
 value   = 1
 reqs    =
     { "type", "name", "range"
@@ -1461,7 +1509,7 @@
     }
 
 [effect_superconductor_slowdown]
-name    = "Slow_Down_Timeline"
+name    = "Slow_Down_Timeline_2"
 value   = 1
 reqs    =
     { "type", "name", "range"
@@ -1469,7 +1517,7 @@
     }
 
 [effect_spaceflight_slowdown]
-name    = "Slow_Down_Timeline"
+name    = "Slow_Down_Timeline_2"
 value   = 1
 reqs    =
     { "type", "name", "range"
Index: common/effects.c
===================================================================
--- common/effects.c	(revision 13023)
+++ common/effects.c	(arbetskopia)
@@ -93,6 +93,7 @@
   "No_Incite",
   "Gain_AI_Love",
   "Slow_Down_Timeline",
+  "Slow_Down_Timeline_2",
   "Civil_War_Chance",
   "Empire_Size_Base",
   "Empire_Size_Step",
Index: common/effects.h
===================================================================
--- common/effects.h	(revision 13023)
+++ common/effects.h	(arbetskopia)
@@ -81,6 +81,7 @@
   EFT_NO_INCITE,
   EFT_GAIN_AI_LOVE,
   EFT_SLOW_DOWN_TIMELINE,
+  EFT_SLOW_DOWN_TIMELINE_2, /* Space module tech slowdown */
   EFT_CIVIL_WAR_CHANCE,
   EFT_EMPIRE_SIZE_BASE, /* +1 unhappy when more than this cities */
   EFT_EMPIRE_SIZE_STEP, /* adds additional +1 unhappy steps to above */
Index: common/game.c
===================================================================
--- common/game.c	(revision 13023)
+++ common/game.c	(arbetskopia)
@@ -222,7 +222,7 @@
   game.info.tcptimeout    = GAME_DEFAULT_TCPTIMEOUT;
   game.info.netwait       = GAME_DEFAULT_NETWAIT;
   game.info.end_year      = GAME_DEFAULT_END_YEAR;
-  game.info.year          = GAME_START_YEAR;
+  game.info.year          = GAME_DEFAULT_START_YEAR;
   game.info.turn          = 0;
   game.info.min_players   = GAME_DEFAULT_MIN_PLAYERS;
   game.info.max_players   = GAME_DEFAULT_MAX_PLAYERS;
@@ -424,8 +424,10 @@
 ***************************************************************/
 int game_next_year(int year)
 {
-  const int slowdown = (game.info.spacerace
-			? get_world_bonus(EFT_SLOW_DOWN_TIMELINE) : 0);
+  const int calendar_slowdown = get_world_bonus(EFT_SLOW_DOWN_TIMELINE);
+  const int space_slowdown = (game.info.spacerace
+			? get_world_bonus(EFT_SLOW_DOWN_TIMELINE_2) : 0);
+	int inc, round = 1;
 
   if (year == 1) /* hacked it to get rid of year 0 */
     year = 0;
@@ -443,23 +445,43 @@
    * about 1900 AD
    */
 
-  /* Note the slowdown operates even if Enable_Space is not active.  See
-   * README.effects for specifics. */
-  if (year >= 1900 || (slowdown >= 3 && year > 0)) {
-    year += 1;
-  } else if (year >= 1750 || slowdown >= 2) {
-    year += 2;
-  } else if (year >= 1500 || slowdown >= 1) {
-    year += 5;
-  } else if( year >= 1000 )
-    year += 10;
-  else if( year >= 0 )
-    year += 20;
-  else if( year >= -1000 ) /* used this line for tuning (was -1250) */
-    year += 25;
-  else
-    year += 50; 
-
+  /* Calendar slowdowns commented with years in the default ruleset
+   * Note the space_slowdown operates even if Enable_Space is not active.  
+   * See README.effects for specifics. */
+  if (calendar_slowdown >= 6 || space_slowdown >= 3 ) { /*1900*/
+    if (year == 0) /* year 0 hack */
+      inc = 2;
+    else
+      inc = 1;
+  } else if (calendar_slowdown >= 5 || space_slowdown >= 2) { /* >=  1750 */
+    inc = 2;
+  } else if (calendar_slowdown >= 4 || space_slowdown >= 1) { /* >=  1500 */
+    inc = 5;
+  } else if( calendar_slowdown >= 3 ) {    /* >=  1000 */
+    inc = 10;
+    round = 5;
+  } else if( calendar_slowdown >= 2 ) {    /* >=     0 */
+    inc = 20;
+    round = 5;
+  } else if( calendar_slowdown >= 1 ) {    /* >= -1000 */
+    inc = 25;
+    round = 5;
+  } else {
+    inc = 50;
+    round = 10;
+  }
+  
+  year += inc;
+   
+  /* Round years, but always positive */
+  if( year % round != 0 ) {
+    int mod = year % round;
+    if (mod < 0)
+      year -= mod;
+    else
+      year += (round - mod);
+  }
+  
   if (year == 0) 
     year = 1;
 
Index: common/game.h
===================================================================
--- common/game.h	(revision 13023)
+++ common/game.h	(arbetskopia)
@@ -179,9 +179,9 @@
 
 #define GAME_DEFAULT_ANGRYCITIZEN TRUE
 
-#define GAME_DEFAULT_END_YEAR    5000
-#define GAME_MIN_END_YEAR        GAME_START_YEAR
-#define GAME_MAX_END_YEAR        5000
+#define GAME_DEFAULT_END_YEAR    GAME_MAX_YEAR
+#define GAME_MIN_END_YEAR        GAME_MIN_YEAR
+#define GAME_MAX_END_YEAR        GAME_MAX_YEAR
 
 #define GAME_DEFAULT_MIN_PLAYERS     1
 #define GAME_MIN_MIN_PLAYERS         1
@@ -328,10 +328,10 @@
 #define GAME_MIN_BARBARIANRATE       0
 #define GAME_MAX_BARBARIANRATE       4
 
-#define GAME_DEFAULT_ONSETBARBARIAN  (GAME_START_YEAR+ \
-				      ((GAME_DEFAULT_END_YEAR-(GAME_START_YEAR))/3))
-#define GAME_MIN_ONSETBARBARIAN      GAME_START_YEAR
-#define GAME_MAX_ONSETBARBARIAN      GAME_MAX_END_YEAR
+#define GAME_DEFAULT_ONSETBARBARIAN  (GAME_DEFAULT_START_YEAR+ \
+				      ((GAME_DEFAULT_END_YEAR-(GAME_DEFAULT_START_YEAR))/3))
+#define GAME_MIN_ONSETBARBARIAN      GAME_MIN_YEAR
+#define GAME_MAX_ONSETBARBARIAN      GAME_MAX_YEAR
 
 #define GAME_DEFAULT_OCCUPYCHANCE    0
 #define GAME_MIN_OCCUPYCHANCE        0
@@ -362,6 +362,11 @@
 #define GAME_MIN_REVOLUTION_LENGTH 0
 #define GAME_MAX_REVOLUTION_LENGTH 10
 
-#define GAME_START_YEAR -4000
+#define GAME_DEFAULT_START_YEAR GAME_MIN_YEAR
+#define GAME_MIN_START_YEAR GAME_MIN_YEAR
+#define GAME_MAX_START_YEAR GAME_MAX_YEAR
 
+#define GAME_MIN_YEAR -4000
+#define GAME_MAX_YEAR 5000
+
 #endif  /* FC__GAME_H */
Index: common/requirements.c
===================================================================
--- common/requirements.c	(revision 13023)
+++ common/requirements.c	(arbetskopia)
@@ -43,7 +43,8 @@
   "Specialist",
   "MinSize",
   "AI",
-  "TerrainClass"
+  "TerrainClass",
+  "MinYear"
 };
 
 /* Names of requirement ranges. These must correspond to enum req_range in
@@ -188,6 +189,12 @@
       return source;
     }
     break;
+  case REQ_MINYEAR:
+    source.value.minyear = atoi(value);
+    if (source.value.minyear >= GAME_MIN_YEAR) {
+      return source;
+    }
+    break;
   case REQ_LAST:
     break;
   }
@@ -252,6 +259,9 @@
   case REQ_TERRAINCLASS:
     source.value.terrainclass = value;
     return source;
+  case REQ_MINYEAR:
+    source.value.minyear = value;
+    return source;
   case REQ_LAST:
     return source;
   }
@@ -317,6 +327,9 @@
   case REQ_TERRAINCLASS:
     *value = source->value.terrainclass;
     return;
+  case REQ_MINYEAR:
+    *value = source->value.minyear;
+    return;
   case REQ_LAST:
     break;
   }
@@ -369,6 +382,9 @@
     case REQ_AI:
       req.range = REQ_RANGE_PLAYER;
       break;
+    case REQ_MINYEAR:
+      req.range = REQ_RANGE_WORLD;
+      break;
     }
   }
 
@@ -411,6 +427,9 @@
   case REQ_SPECIALIST:
     invalid = (req.range != REQ_RANGE_LOCAL);
     break;
+  case REQ_MINYEAR:
+    invalid = (req.range != REQ_RANGE_WORLD);
+    break;
   case REQ_NONE:
     invalid = FALSE;
     break;
@@ -929,6 +948,9 @@
                                      req->range, req->survives,
                                      req->source.value.terrainclass);
     break;
+  case REQ_MINYEAR:
+    eval = game.info.year >= req->source.value.minyear;
+    break;
   case REQ_LAST:
     assert(0);
     return FALSE;
@@ -1009,6 +1031,8 @@
      * reasons and so that the AI doesn't get confused (since the AI
      * doesn't know how to meet special and terrain requirements). */
     return TRUE;
+  case REQ_MINYEAR: /* Passed if negated */
+    return req->negated; 
   case REQ_LAST:
     break;
   }
@@ -1057,6 +1081,8 @@
     return psource1->value.level == psource2->value.level;
   case REQ_TERRAINCLASS:
     return psource1->value.terrainclass == psource2->value.terrainclass;
+  case REQ_MINYEAR:
+    return psource1->value.minyear == psource2->value.minyear;
   case REQ_LAST:
     break;
   }
@@ -1124,6 +1150,14 @@
      cat_snprintf(buf, bufsz, _("%s terrain"),
                   terrain_class_name(psource->value.terrainclass));
      break;
+  case REQ_MINYEAR:
+    if (psource->value.minyear < 0)
+      cat_snprintf(buf, bufsz, _("Earliest year %d BC"),
+		    -psource->value.minyear);
+    else 
+      cat_snprintf(buf, bufsz, _("Earliest year %d AD"),
+        psource->value.minyear);
+    break;
   case REQ_LAST:
     assert(0);
     break;
Index: common/requirements.h
===================================================================
--- common/requirements.h	(revision 13023)
+++ common/requirements.h	(arbetskopia)
@@ -37,6 +37,7 @@
   REQ_MINSIZE,      /* Minimum size: at city range means city size */
   REQ_AI,           /* AI level of the player */
   REQ_TERRAINCLASS, /* More generic terrain type, currently "Land" or "Ocean" */
+  REQ_MINYEAR,      /* Game year greater than or equal */
   REQ_LAST
 };
 
@@ -71,6 +72,7 @@
     int minsize;                        /* source minsize type */
     enum ai_level level;                /* source AI level */
     enum terrain_class terrainclass;    /* source generic terrain type */
+    int minyear;                        /* source minimum year */
   } value;                              /* source value */
 };
 
Index: ai/aicity.c
===================================================================
--- ai/aicity.c	(revision 13023)
+++ ai/aicity.c	(arbetskopia)
@@ -369,6 +369,7 @@
     break;
 
   case EFT_SLOW_DOWN_TIMELINE:
+  case EFT_SLOW_DOWN_TIMELINE_2:
     /* AI doesn't care about these. */
     break;
 
Index: client/helpdata.c
===================================================================
--- client/helpdata.c	(revision 13023)
+++ client/helpdata.c	(arbetskopia)
@@ -218,6 +218,23 @@
      cat_snprintf(buf, bufsz, _("Requires %s terrain.\n\n"),
                   terrain_class_name(req->source.value.terrainclass));
      return;
+  case REQ_MINYEAR:
+    if (req->negated) {
+      if (req->source.value.minyear < 0)
+        cat_snprintf(buf, bufsz, _("Only available before year %d BC.\n\n"),
+		     req->source.value.minsize);
+      else
+        cat_snprintf(buf, bufsz, _("Only available before year %d AD.\n\n"),
+		     req->source.value.minsize);
+    } else {
+      if (req->source.value.minyear < 0)
+        cat_snprintf(buf, bufsz, _("Only available after year %d BC.\n\n"),
+		     req->source.value.minsize);
+      else
+        cat_snprintf(buf, bufsz, _("Only available after year %d AD.\n\n"),
+		     req->source.value.minsize);
+    }
+    return;
   }
   assert(0);
 }
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to