<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