Skip to content

Commit 211d7e4

Browse files
committed
Implement handling for variable seed growth stages
-Switch to using a vector of pairs to preserve insertion order (also order of iteration, VERY IMPORTANT!) -Remove the remnants of "grow", document growth_stages
1 parent 991994f commit 211d7e4

File tree

9 files changed

+107
-99
lines changed

9 files changed

+107
-99
lines changed

doc/JSON/ITEM.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -974,9 +974,13 @@ Gun mods can be defined like this:
974974
"fruit": "blackberries", // The item id of the fruits that this seed will produce.
975975
"seeds": false, // (optional, default is true). If true, harvesting the plant will spawn seeds (the same type as the item used to plant). If false only the fruits are spawned, no seeds.
976976
"byproducts": [ "withered" ], // A list of further items that should spawn upon harvest.
977-
"grow": "91 days", // A time duration: how long it takes for a plant to fully mature. Based around a 91 day season length (roughly a real world season) to give better accuracy for longer season lengths
978-
// Note that growing time is later converted based upon the season_length option, basing it around 91 is just for accuracy purposes
979-
// A value 91 means 3 full seasons, a value of 30 would mean 1 season.
977+
"growth_stages": [
978+
{ "GROWTH_SEED": "5 days" }, // The stages of growth that this plant will go through and how long each stage will last. The actual stages are currently defined by the furniture representing the growing plant, so
979+
{ "GROWTH_SEEDLING": "10 days" }, // it is highly recommended that you use exactly the same stages as listed here. The duration of each stage is fully fungible, and you can have a plant that stays in <form> for a very
980+
{ "GROWTH_MATURE": "99 days" }, // long time, or a very short time, etc. Planting currently checks the temperature during each stage advance, so a crop that should be plantable before winter to harvest in the spring
981+
{ "GROWTH_HARVEST": "2 days" }, // will want at least 91 days (the length of winter) in between some stages. This example would overwinter in the GROWTH_MATURE form.
982+
{ "GROWTH_OVERGROWN": "1 hour" } // NOTE: GROWTH_OVERGROWN is optional, but if used should be a short duration. Or else it may fail to plant because the overgrown(dead) crops would not 'grow'!
983+
],
980984
"fruit_div": 2, // (optional, default is 1). Final amount of fruit charges produced is divided by this number. Works only if fruit item is counted by charges.
981985
"required_terrain_flag": "PLANTABLE" // A tag that terrain and furniture would need to have in order for the seed to be plantable there.
982986
// Default is "PLANTABLE", and using this will cause any terain the plant is wrown on to turn into dirt once the plant is planted, unless furniture is used.

src/activity_handlers.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,8 +2063,9 @@ void activity_handlers::plant_seed_finish( player_activity *act, Character *you
20632063
}
20642064
used_seed.front().set_flag( json_flag_HIDDEN_ITEM );
20652065
here.add_item_or_charges( examp, used_seed.front() );
2066-
if( here.has_flag_furn( seed_id->seed->required_terrain_flag, examp ) ) {
2067-
here.furn_set( examp, furn_str_id( here.furn( examp )->plant->transform ) );
2066+
if( here.has_flag_furn( seed_id->seed->required_terrain_flag, examp ) &&
2067+
here.furn( examp )->plant != nullptr ) {
2068+
here.furn_set( examp, here.furn( examp )->plant->transform );
20682069
} else if( seed_id->seed->required_terrain_flag == ter_furn_flag::TFLAG_PLANTABLE ) {
20692070
here.set( examp, ter_t_dirt, furn_f_plant_seed );
20702071
} else {

src/item.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15155,20 +15155,6 @@ bool item::is_seed() const
1515515155
return !!type->seed;
1515615156
}
1515715157

15158-
time_duration item::get_plant_epoch( int num_epochs ) const
15159-
{
15160-
if( !type->seed ) {
15161-
return 0_turns;
15162-
}
15163-
// Growing times have been based around real world season length rather than
15164-
// the default in-game season length to give
15165-
// more accuracy for longer season lengths
15166-
// Also note that seed->grow is the time it takes from seeding to harvest, this is
15167-
// divided by number of growth stages (usually 3) to get the time it takes from one plant state to the next.
15168-
// TODO: move this into the islot_seed
15169-
return type->seed->grow * calendar::season_ratio() / num_epochs;
15170-
}
15171-
1517215158
std::string item::get_plant_name() const
1517315159
{
1517415160
if( !type->seed ) {

src/item.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,11 +2163,6 @@ class item : public visitable
21632163
* Whether this is actually a seed, the seed functions won't be of much use for non-seeds.
21642164
*/
21652165
bool is_seed() const;
2166-
/**
2167-
* Time it takes to grow from one stage to another. There are normally 4 plant stages:
2168-
* seed, seedling, mature and harvest. Non-seed items return 0.
2169-
*/
2170-
time_duration get_plant_epoch( int num_epochs = 3 ) const;
21712166
/**
21722167
* The name of the plant as it appears in the various informational menus. This should be
21732168
* translated. Returns an empty string for non-seed items.

src/item_factory.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2528,8 +2528,10 @@ void Item_factory::check_definitions() const
25282528
}
25292529
}
25302530
if( type->seed ) {
2531-
if( type->seed->grow < 1_turns ) {
2532-
msg += "seed growing time is less than 1 turn\n";
2531+
for( const auto &pair : type->seed->get_growth_stages() ) {
2532+
if( pair.second < 1_turns ) {
2533+
msg += string_format( "Growth for stage %s is less than 1 turn", pair.first.c_str() );
2534+
}
25332535
}
25342536
if( !has_template( type->seed->fruit_id ) ) {
25352537
msg += string_format( "invalid fruit id %s\n", type->seed->fruit_id.c_str() );
@@ -3456,12 +3458,11 @@ void islot_compostable::deserialize( const JsonObject &jo )
34563458

34573459
void islot_seed::deserialize( const JsonObject &jo )
34583460
{
3459-
optional( jo, was_loaded, "grow", grow, 1_days );
34603461
optional( jo, was_loaded, "fruit_div", fruit_div, 1 );
34613462
mandatory( jo, was_loaded, "plant_name", plant_name );
34623463
mandatory( jo, was_loaded, "fruit", fruit_id );
34633464
mandatory( jo, was_loaded, "growth_stages", growth_stages,
3464-
generic_map_reader<flag_id, time_duration> {} );
3465+
vector_pair_reader<flag_id, time_duration> {} );
34653466
optional( jo, was_loaded, "seeds", spawn_seeds, true );
34663467
optional( jo, was_loaded, "byproducts", byproducts );
34673468
optional( jo, was_loaded, "required_terrain_flag", required_terrain_flag,

src/itype.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ bool itype::is_basic_component() const
285285
return false;
286286
}
287287

288+
const std::vector<std::pair<flag_id, time_duration>> &islot_seed::get_growth_stages() const
289+
{
290+
return growth_stages;
291+
}
292+
288293
int islot_armor::avg_env_resist() const
289294
{
290295
int acc = 0;

src/itype.h

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,43 +1161,42 @@ struct islot_bionic {
11611161
};
11621162

11631163
struct islot_seed {
1164-
// Generic factory stuff
1165-
bool was_loaded = false;
1166-
void deserialize( const JsonObject &jo );
1164+
// Generic factory stuff
1165+
bool was_loaded = false;
1166+
void deserialize( const JsonObject &jo );
11671167

1168-
/**
1169-
* Time it takes for a seed to grow (based of off a season length of 91 days).
1170-
*/
1171-
time_duration grow = 0_turns;
1172-
/**
1173-
* Amount of harvested charges of fruits is divided by this number.
1174-
*/
1175-
int fruit_div = 1;
1176-
/**
1177-
* Name of the plant.
1178-
*/
1179-
translation plant_name;
1180-
/**
1181-
* What stages of growth does this plant have? What is the furniture associated with that stage of growth?
1182-
*/
1183-
std::map<flag_id, time_duration> growth_stages;
1184-
/**
1185-
* Type id of the fruit item.
1186-
*/
1187-
itype_id fruit_id;
1188-
/**
1189-
* Whether to spawn seed items additionally to the fruit items.
1190-
*/
1191-
bool spawn_seeds = true;
1192-
/**
1193-
* Additionally items (a list of their item ids) that will spawn when harvesting the plant.
1194-
*/
1195-
std::vector<itype_id> byproducts;
1196-
/**
1197-
* Terrain tag required to plant the seed.
1198-
*/
1199-
ter_furn_flag required_terrain_flag = ter_furn_flag::TFLAG_PLANTABLE;
1200-
islot_seed() = default;
1168+
/**
1169+
* Amount of harvested charges of fruits is divided by this number.
1170+
*/
1171+
int fruit_div = 1;
1172+
/**
1173+
* Name of the plant.
1174+
*/
1175+
translation plant_name;
1176+
/**
1177+
* Type id of the fruit item.
1178+
*/
1179+
itype_id fruit_id;
1180+
/**
1181+
* Whether to spawn seed items additionally to the fruit items.
1182+
*/
1183+
bool spawn_seeds = true;
1184+
/**
1185+
* Additionally items (a list of their item ids) that will spawn when harvesting the plant.
1186+
*/
1187+
std::vector<itype_id> byproducts;
1188+
/**
1189+
* Terrain tag required to plant the seed.
1190+
*/
1191+
ter_furn_flag required_terrain_flag = ter_furn_flag::TFLAG_PLANTABLE;
1192+
islot_seed() = default;
1193+
1194+
const std::vector<std::pair<flag_id, time_duration>> &get_growth_stages() const;
1195+
private:
1196+
/**
1197+
* What stages of growth does this plant have? How long does each stage of growth last?
1198+
*/
1199+
std::vector<std::pair<flag_id, time_duration>> growth_stages;
12011200
};
12021201

12031202
enum condition_type {

src/map.cpp

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8480,40 +8480,56 @@ void map::grow_plant( const tripoint_bub_ms &p )
84808480
furn_set( p, furn_str_id::NULL_ID() );
84818481
return;
84828482
}
8483-
// TODO: this should probably be read from the seed's data. But for now, everything uses exactly this many growth stages.
8484-
std::map<ter_furn_flag, int> plant_epochs;
8485-
plant_epochs[ter_furn_flag::TFLAG_GROWTH_SEEDLING] = 1;
8486-
plant_epochs[ter_furn_flag::TFLAG_GROWTH_MATURE] = 2;
8487-
plant_epochs[ter_furn_flag::TFLAG_GROWTH_HARVEST] = 3;
8488-
8489-
const time_duration base_epoch_duration = seed->get_plant_epoch( plant_epochs.size() );
8490-
const time_duration epoch_duration = base_epoch_duration * furn.plant->growth_multiplier;
8491-
if( seed->age() >= epoch_duration ) {
8492-
const int epoch_age = seed->age() / epoch_duration;
8493-
int current_epoch = 0;
8494-
for( std::pair<const ter_furn_flag, int> pair : plant_epochs ) {
8495-
if( has_flag_furn( pair.first, p ) ) {
8496-
current_epoch = pair.second;
8497-
break;
8498-
}
8499-
}
8500-
const int epochs_to_advance = epoch_age - current_epoch;
85018483

8502-
for( int i = 0; i < epochs_to_advance; i++ ) {
8503-
// Remove fertilizer if any
8504-
map_stack::iterator fertilizer = std::find_if( items.begin(), items.end(), []( const item & it ) {
8505-
return it.has_flag( flag_FERTILIZER );
8506-
} );
8507-
if( fertilizer != items.end() ) {
8508-
items.erase( fertilizer );
8509-
}
8510-
// spawn appropriate amount of rot_spawn, equivalent to number of times we iterate this loop
8511-
rotten_item_spawn( *seed, tripoint_bub_ms( p ) );
8512-
// Get an updated reference to the furniture each time we go through this loop, to make sure we transform each step in turn
8513-
const furn_t &current_furn = this->furn( p ).obj();
8514-
furn_set( p, furn_str_id( current_furn.plant->transform ) );
8484+
const std::vector<std::pair<flag_id, time_duration>> &growth_stages =
8485+
seed->type->seed->get_growth_stages();
8486+
flag_id current_stage = flag_id( io::enum_to_string<ter_furn_flag>
8487+
( ter_furn_flag::TFLAG_GROWTH_SEED ) );
8488+
flag_id target_stage = flag_id( io::enum_to_string<ter_furn_flag>
8489+
( ter_furn_flag::TFLAG_GROWTH_SEED ) );
8490+
time_duration time_to_grow_to_this_stage = 0_seconds;
8491+
for( const auto &pair : growth_stages ) {
8492+
const time_duration stage_growth_time = pair.second;
8493+
time_to_grow_to_this_stage += stage_growth_time;
8494+
if( has_flag_furn( pair.first.str(), p ) ) {
8495+
current_stage = pair.first;
8496+
}
8497+
if( seed->age() >= time_to_grow_to_this_stage ) {
8498+
target_stage = pair.first;
85158499
}
8500+
}
85168501

8502+
const auto check_flag = []( const std::string & to_check ) {
8503+
return [&to_check]( const auto & pair_flag_and_dur ) {
8504+
return pair_flag_and_dur.first.str() == to_check;
8505+
};
8506+
};
8507+
8508+
const int stages_to_advance = std::distance(
8509+
std::find_if( growth_stages.begin(), growth_stages.end(), check_flag( current_stage.str() ) ),
8510+
std::find_if( growth_stages.begin(), growth_stages.end(), check_flag( target_stage.str() ) ) );
8511+
8512+
add_msg_debug( debugmode::DF_MAP,
8513+
"Checking growth on a %s, aged %s. Current stage: %s. Target stage: %s. Advancing %d stages.",
8514+
// Human readable time please. '7863287 s' from to_string_writable() is not a reasonable output.
8515+
//NOLINTNEXTLINE(cata-translations-in-debug-messages)
8516+
seed->tname(), to_string( seed->age() ), current_stage.c_str(), target_stage.c_str(),
8517+
stages_to_advance );
8518+
8519+
8520+
for( int i = 0; i < stages_to_advance; i++ ) {
8521+
// Remove fertilizer if any
8522+
map_stack::iterator fertilizer = std::find_if( items.begin(), items.end(), []( const item & it ) {
8523+
return it.has_flag( flag_FERTILIZER );
8524+
} );
8525+
if( fertilizer != items.end() ) {
8526+
items.erase( fertilizer );
8527+
}
8528+
// spawn appropriate amount of rot_spawn, equivalent to number of times we iterate this loop
8529+
rotten_item_spawn( *seed, tripoint_bub_ms( p ) );
8530+
// Get an updated reference to the furniture each time we go through this loop, to make sure we transform each step in turn
8531+
const furn_t &current_furn = this->furn( p ).obj();
8532+
furn_set( p, furn_str_id( current_furn.plant->transform ) );
85178533
}
85188534
}
85198535

src/weather.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "item.h"
2525
#include "item_contents.h"
2626
#include "item_location.h"
27+
#include "itype.h"
2728
#include "line.h"
2829
#include "map.h"
2930
#include "map_scale_constants.h"
@@ -46,6 +47,7 @@
4647
#include "trap.h"
4748
#include "uistate.h"
4849
#include "units.h"
50+
#include "value_ptr.h"
4951
#include "weather_gen.h"
5052

5153
static const activity_id ACT_WAIT_WEATHER( "ACT_WAIT_WEATHER" );
@@ -885,13 +887,12 @@ bool warm_enough_to_plant( const tripoint_abs_omt &pos, const itype_id &it )
885887
time_point check_date = calendar::turn;
886888
planting_times[check_date] = get_weather().get_temperature( pos );
887889
bool okay_to_plant = true;
888-
const int num_epochs = 3; // FIXME. Should be stored on the seed ptr and read from there!
890+
const std::vector<std::pair<flag_id, time_duration>> &growth_stages = it->seed->get_growth_stages();
889891
// and now iterate a copy of the weather into the future to see if they'll be plantable then as well.
890-
time_duration one_growth_cycle = item( it ).get_plant_epoch( num_epochs );
891892
const weather_generator weather_gen = get_weather().get_cur_weather_gen();
892-
for( int i = 0; i < num_epochs; i++ ) {
893+
for( const auto &pair : growth_stages ) {
893894
// TODO: Replace epoch checks with data from a farmer's almanac
894-
check_date = check_date + one_growth_cycle;
895+
check_date = check_date + pair.second;
895896
const w_point &w = weather_gen.get_weather( project_to<coords::ms>( pos ), check_date,
896897
g->get_seed() );
897898
planting_times[check_date] = w.temperature;

0 commit comments

Comments
 (0)