void ribi::imcw::company::buy_click_card( person& customer, balance& account_euros, bank& the_bank, calendar& the_calendar ) { assert(customer.has_account(account_euros)); #ifndef NDEBUG const auto before = account_euros.get_value(); #endif the_bank.transfer( account_euros, money(click_card::cost_inc_vat_euros), m_balance_undistributed, the_calendar.get_today() ); #ifndef NDEBUG const auto after = account_euros.get_value(); assert(after < before); #endif //ClickCard will be valid the first day of the next month click_card c( the_calendar.get_today() //Purchase date ); customer.add_click_card(c); }
w_point weather_generator::get_weather(const point &location, const calendar &t) const { const double x(location.x / 2000.0);// Integer x position / widening factor of the Perlin function. const double y(location.y / 2000.0);// Integer y position / widening factor of the Perlin function. // Leaving these in just in case something ELSE goes wrong--KA101 // int initial_season(0); // if(ACTIVE_WORLD_OPTIONS["INITIAL_SEASON"].getValue() == "spring") { // initial_season = 1; // } else if(ACTIVE_WORLD_OPTIONS["INITIAL_SEASON"].getValue() == "summer") { // initial_season = 2; // } else if(ACTIVE_WORLD_OPTIONS["INITIAL_SEASON"].getValue() == "autumn") { // initial_season = 3; // } const double z( double( t.get_turn() + DAYS(t.season_length()) ) / 2000.0); // Integer turn / widening factor of the Perlin function. const double dayFraction((double)t.minutes_past_midnight() / 1440); // Noise factors double T(raw_noise_4d(x, y, z, SEED) * 4.0); double H(raw_noise_4d(x, y, z / 5, SEED + 101)); double H2(raw_noise_4d(x, y, z, SEED + 151) / 4); double P(raw_noise_4d(x, y, z / 3, SEED + 211) * 70); double W; const double now( double( t.turn_of_year() + DAYS(t.season_length()) / 2 ) / double(t.year_turns()) ); // [0,1) const double ctn(cos(tau * now)); // Temperature variation const double mod_t(0); // TODO: make this depend on latitude and altitude? const double current_t(base_t + mod_t); // Current baseline temperature. Degrees Celsius. const double seasonal_variation(ctn * -1); // Start and end at -1 going up to 1 in summer. const double season_atenuation(ctn / 2 + 1); // Harsh winter nights, hot summers. const double season_dispersion(pow(2, ctn + 1) - 2.3); // Make summers peak faster and winters not perma-frozen. const double daily_variation(cos( tau * dayFraction - tau / 8 ) * -1 * season_atenuation / 2 + season_dispersion * -1); // Day-night temperature variation. T += current_t; // Add baseline to the noise. T += seasonal_variation * 8 * exp(-pow(current_t * 2.7 / 10 - 0.5, 2)); // Add season curve offset to account for the winter-summer difference in day-night difference. T += daily_variation * 8 * exp(-pow(current_t / 30, 2)); // Add daily variation scaled to the inverse of the current baseline. A very specific and finicky adjustment curve. T = T * 9 / 5 + 32; // Convert to imperial. =| // Humidity variation const double mod_h(0); const double current_h(base_h + mod_h); H = std::max(std::min((ctn / 10.0 + (-pow(H, 2) * 3 + H2)) * current_h / 2.0 + current_h, 100.0), 0.0); // Humidity stays mostly at the mean level, but has low peaks rarely. It's a percentage. // Pressure variation P += seasonal_variation * 20 + base_p; // Pressure is mostly random, but a bit higher on summer and lower on winter. In millibars. // Wind power W = std::max(0, 1020 - (int)P); return w_point {T, H, P, W, false}; }
void ribi::imcw::company::distribute_net_profit( bank& the_bank, calendar& the_calendar ) noexcept { const money profit = m_balance_undistributed.get_value(); const money to_compensation_plan = profit * proportion_of_profit_to_compensation_plan; const money to_holding = profit * proportion_of_profit_to_holding; const money to_reserves = profit * proportion_of_profit_to_reserves; const money to_winners = profit * proportion_of_profit_to_winners; the_bank.transfer( m_balance_undistributed, //Sender to_compensation_plan, m_balance_compensation_plan, //Target the_calendar.get_today() ); the_bank.transfer( m_balance_undistributed, //Sender to_holding, m_balance_holding, //Target the_calendar.get_today() ); the_bank.transfer( m_balance_undistributed, //Sender to_reserves, m_balance_reserves, //Target the_calendar.get_today() ); //Shortly transfer it to the Winners balance... the_bank.transfer( m_balance_undistributed, //Sender to_winners, m_balance_winners, //Target the_calendar.get_today() ); //then distribute all over the customers distribute_net_profit_winners( m_balance_winners, //Sender to_winners, the_bank, the_calendar ); distribute_net_profit_compensation_plan( m_balance_compensation_plan, //Sender to_compensation_plan, the_bank, the_calendar ); }
inline void proc_weather_sum( const weather_type wtype, weather_sum &data, const calendar &turn, const int tick_size ) { switch( wtype ) { case WEATHER_DRIZZLE: data.rain_amount += 4 * tick_size; break; case WEATHER_RAINY: case WEATHER_THUNDER: case WEATHER_LIGHTNING: data.rain_amount += 8 * tick_size; break; case WEATHER_ACID_DRIZZLE: data.acid_amount += 4 * tick_size; break; case WEATHER_ACID_RAIN: data.acid_amount += 8 * tick_size; break; default: break; } // TODO: Change this calendar::sunlight "sampling" here into a proper interpolation const float tick_sunlight = turn.sunlight() - weather_data( wtype ).light_modifier; data.sunlight += std::max<float>( 0.0f, tick_size * tick_sunlight ); }
weather_type weather_generator::get_weather_conditions(const point &location, const calendar &t) const { w_point w(get_weather(location, t)); weather_type wt = get_weather_conditions(w); // Make sure we don't say it's sunny at night! =P if (wt == WEATHER_SUNNY && t.is_night()) { return WEATHER_CLEAR; } return wt; }
void ribi::imcw::company::transfer( balance& source, bank& the_bank, calendar& the_calendar ) noexcept { const money the_money = source.get_value(); the_bank.transfer( source, the_money, m_balance_undistributed, the_calendar.get_today() ); }
/** * Determine what a funnel has filled out of game, using funnelcontainer.bday as a starting point. */ void retroactively_fill_from_funnel( item *it, const trap_id t, const calendar &endturn, const point &location ) { const calendar startturn = calendar( it->bday > 0 ? it->bday - 1 : 0 ); if ( startturn > endturn || traplist[t]->funnel_radius_mm < 1 ) { return; } it->bday = int(endturn.get_turn()); // bday == last fill check int rain_amount = 0; int acid_amount = 0; int rain_turns = 0; int acid_turns = 0; for( calendar turn(startturn); turn >= endturn; turn += 10) { switch(g->weatherGen.get_weather_conditions(location, turn)) { case WEATHER_DRIZZLE: rain_amount += 4; rain_turns++; break; case WEATHER_RAINY: case WEATHER_THUNDER: case WEATHER_LIGHTNING: rain_amount += 8; rain_turns++; break; case WEATHER_ACID_DRIZZLE: acid_amount += 4; acid_turns++; break; case WEATHER_ACID_RAIN: acid_amount += 8; acid_turns++; break; default: break; } } int rain = rain_turns / traplist[t]->funnel_turns_per_charge( rain_amount ); int acid = acid_turns / traplist[t]->funnel_turns_per_charge( acid_amount ); it->add_rain_to_container( false, rain ); it->add_rain_to_container( true, acid ); }
void Messages::display_messages() { WINDOW_PTR w_ptr {newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, (TERMY > FULL_SCREEN_HEIGHT) ? (TERMY - FULL_SCREEN_HEIGHT) / 2 : 0, (TERMX > FULL_SCREEN_WIDTH) ? (TERMX - FULL_SCREEN_WIDTH) / 2 : 0)}; WINDOW *const w = w_ptr.get(); input_context ctxt("MESSAGE_LOG"); ctxt.register_action("UP", _("Scroll up")); ctxt.register_action("DOWN", _("Scroll down")); ctxt.register_action("QUIT"); ctxt.register_action("HELP_KEYBINDINGS"); int offset = 0; const int maxlength = FULL_SCREEN_WIDTH - 2 - 1; const int bottom = FULL_SCREEN_HEIGHT - 2; const int msg_count = size(); for (;;) { werase(w); draw_border(w); mvwprintz(w, bottom + 1, 32, c_red, _("Press %s to return"), ctxt.get_desc("QUIT").c_str()); draw_scrollbar(w, offset, bottom, msg_count, 1); int line = 1; int lasttime = -1; for( int i = offset; i < msg_count; ++i ) { if (line > bottom) { break; } const game_message &m = player_messages.impl_->history(i); const nc_color col = m.get_color(player_messages.impl_->curmes); const calendar timepassed = calendar::turn - m.time; if (timepassed.get_turn() > lasttime) { mvwprintz(w, line++, 3, c_ltblue, _("%s ago:"), timepassed.textify_period().c_str()); lasttime = timepassed.get_turn(); } for( const std::string &folded : foldstring(m.get_with_count(), maxlength) ) { if (line > bottom) { break; } mvwprintz(w, line++, 1, col, "%s", folded.c_str()); } } if (offset + 1 < msg_count) { mvwprintz(w, bottom + 1, 5, c_magenta, "vvv"); } if (offset > 0) { mvwprintz(w, bottom + 1, maxlength - 3, c_magenta, "^^^"); } wrefresh(w); const std::string &action = ctxt.handle_input(); if (action == "DOWN" && offset + 1 < msg_count) { offset++; } else if (action == "UP" && offset > 0) { offset--; } else if (action == "QUIT") { break; } } player_messages.impl_->curmes = calendar::turn.get_turn(); }
void ribi::imcw::company::distribute_net_profit_winners( balance& source, const money& total_money, bank& the_bank, calendar& the_calendar ) noexcept { //Collect the Winners from all customers std::vector<std::reference_wrapper<winner>> winners = collect_winners(); //Distribute the money over the winners //const int n_winners{count_winners()}; const int n_winners{static_cast<int>(winners.size())}; if (n_winners == 0) { //Transfer money to reserves the_bank.transfer( source, total_money, m_balance_reserves, the_calendar.get_today() ); return; } assert(n_winners > 0); const money income_per_winners_euros = total_money / static_cast<double>(n_winners) ; for (std::reference_wrapper<winner>& w: winners) { #ifndef NDEBUG const auto winner_value_before = w.get().get_value(); #endif the_bank.transfer( source, income_per_winners_euros, w.get().get_balance(), the_calendar.get_today() ); #ifndef NDEBUG const auto winner_value_after = w.get().get_value(); assert(winner_value_after >= winner_value_before); #endif } if (m_verbose) { std::clog << "Process the Winners" << std::endl; } for (person& customer: m_customers) { customer.process_winners( the_bank, the_calendar, *this ); } if (m_verbose) { std::clog << "Distributing " << total_money << " over the winners\n" << "n_winners: " << n_winners << '\n' << "income_per_winners_euros: " << income_per_winners_euros << " \n" ; } }
void ribi::imcw::company::distribute_net_profit_compensation_plan( balance& source, const money& total_money, bank& the_bank, calendar& the_calendar ) noexcept { if (count_active_customers(the_calendar.get_today()) == 0) { //Transfer money to reserves the_bank.transfer( source, total_money, m_balance_undistributed, the_calendar.get_today() ); return; } assert(count_active_customers(the_calendar.get_today()) > 0); #ifdef FIX_ISSUE_5 assert(!"Not implemented yet"); #else //STUB: Transfer money to reserves the_bank.transfer( source, total_money, m_balance_undistributed, the_calendar.get_today() ); return; #endif /* const money income_per_winners_euros = total_money / static_cast<double>(n_winners) ; for (std::reference_wrapper<winner>& w: winners) { #ifndef NDEBUG const auto winner_value_before = w.get().get_value(); #endif the_bank.transfer( source, income_per_winners_euros, w.get().get_balance(), the_calendar.get_today() ); #ifndef NDEBUG const auto winner_value_after = w.get().get_value(); assert(winner_value_after >= winner_value_before); #endif } if (m_verbose) { std::clog << "Process the Winners" << std::endl; } for (person& customer: m_customers) { customer.process_winners( the_bank, the_calendar, *this ); } if (m_verbose) { std::clog << "Distributing " << total_money << " over the winners\n" << "n_winners: " << n_winners << '\n' << "income_per_winners_euros: " << income_per_winners_euros << " \n" ; } */ }
void Messages::display_messages() { catacurses::window w = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, ( TERMY > FULL_SCREEN_HEIGHT ) ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0, ( TERMX > FULL_SCREEN_WIDTH ) ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ); input_context ctxt( "MESSAGE_LOG" ); ctxt.register_action( "UP", _( "Scroll up" ) ); ctxt.register_action( "DOWN", _( "Scroll down" ) ); ctxt.register_action( "QUIT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); /* Right-Aligning Time Epochs For Readability * ========================================== * Given display_messages(); right-aligns epochs, we must declare one quick variable first: * max_padlength refers to the length of the LONGEST possible unit of time returned by to_string_clipped() any language has to offer. * This variable is, for now, set to '10', which seems most reasonable. * * The reason right-aligned epochs don't use a "shortened version" (e.g. only showing the first letter) boils down to: * 1. The first letter of every time unit being unique is a property that might not carry across to other languages. * 2. Languages where displayed characters change depending on the characters preceding/following it will become unclear. * 3. Some polymorphic languages might not be able to appropriately convey meaning with one character (FRS is not a linguist- correct her on this if needed.) * * This right padlength is then incorporated into a so-called 'epoch_format' which, in turn, will be used to format the correct epoch. * If an external language introduces time units longer than 10 characters in size, consider altering these variables. * The game (likely) shan't segfault, though the text may appear a bit messed up if these variables aren't set properly. */ const int max_padlength = 10; /* Dealing With Screen Extremities * =============================== * 'maxlength' corresponds to the most extreme length a log message may be before foldstring() wraps it around to two or more lines. * The numbers subtracted from FULL_SCREEN_WIDTH are - in order: * '-2' the characters reserved for the borders of the box, both on the left and right side. * '-1' the leftmost guide character that's drawn on screen. * '-4' the padded three-digit number each epoch starts with. * '-max_padlength' the characters of space that are allocated to time units (e.g. "years", "minutes", etc.) * * 'bottom' works much like 'maxlength', but instead it refers to the amount of lines that the message box may hold. */ const int maxlength = FULL_SCREEN_WIDTH - 2 - 1 - 4 - max_padlength; const int bottom = FULL_SCREEN_HEIGHT - 2; const int msg_count = size(); /* Dealing With Scroll Direction * ============================= * Much like how the sidebar can have variable scroll direction, so will the message box. * To properly differentiate the two methods of displaying text, we will label them NEWEST-TOP, and OLDEST-TOP. This labeling should be self explanatory. * * Note that 'offset' tracks only our current position in the list; it shan't at all affect the manner in which the messages are drawn. * Messages are always drawn top-to-bottom. If NEWEST-TOP is used, then the top line (line=1) corresponds to the newest message. The one further down the second-newest, etc. * If the OLDEST-TOP method is used, then the top line (line=1) corresponds to the oldest message, and the bottom one to the newest. * The 'for (;;)' block below is nearly completely method-agnostic, save for the `player_messages.impl_->history(i)` call. * * In case of NEWEST-TOP, the 'i' variable easily enough corresponds to the newest message. * In case of OLDEST-TOP, the 'i' variable must be flipped- meaning the highest value of 'i' returns the result for the lowest value of 'i', etc. * To achieve this, the 'flip_message' variable is set to either the value of 'msg_count', or '0'. This variable is then subtracted from 'i' in each call to player_messages.impl_->history(); * * 'offset' refers to the corresponding message that will be displayed at the very TOP of the message box window. * NEWEST-TOP: 'offset' starts simply at '0' - the very top of the window. * OLDEST-TOP: 'offset' is set to the maximum value it could possibly be. That is- 'msg_count-bottom'. This way, the screen starts with the scrollbar all the way down. * 'retrieve_history' refers to the line that should be displayed- this is either 'i' if it's NEWEST-TOP, or a flipped version of 'i' if it's OLDEST-TOP. */ int offset = log_from_top ? 0 : ( msg_count - bottom ); const int flip = log_from_top ? 0 : msg_count - 1; for( ;; ) { werase( w ); draw_border( w ); mvwprintz( w, bottom + 1, 32, c_red, _( "Press %s to return" ), ctxt.get_desc( "QUIT" ).c_str() ); draw_scrollbar( w, offset, bottom, msg_count, 1, 0, c_white, true ); int line = 1; int lasttime = -1; for( int i = offset; i < msg_count; ++i ) { const int retrieve_history = abs( i - flip ); if( line > bottom ) { break; // This statement makes it so that no non-existent messages are printed (which usually results in a segfault) } else if( retrieve_history >= msg_count ) { continue; } const game_message &m = player_messages.impl_->history( retrieve_history ); const calendar timepassed = calendar::turn - m.timestamp_in_turns; std::string long_ago = to_string_clipped( time_duration::from_turns( timepassed ) ); nc_color col = msgtype_to_color( m.type, false ); // Here we separate the unit and amount from one another so that they can be properly padded when they're drawn on the screen. // Note that the very first character of 'unit' is often a space (except for languages where the time unit directly follows the number.) const auto amount_len = long_ago.find_first_not_of( "0123456789" ); std::string amount = long_ago.substr( 0, amount_len ); std::string unit = long_ago.substr( amount_len ); if( timepassed.get_turn() != lasttime ) { right_print( w, line, 2, c_light_blue, string_format( _( "%-3s%-10s" ), amount.c_str(), unit.c_str() ) ); lasttime = timepassed.get_turn(); } nc_color col_out = col; for( const std::string &folded : foldstring( m.get_with_count(), maxlength ) ) { if( line > bottom ) { break; } print_colored_text( w, line, 2, col_out, col, folded ); // So-called special "markers"- alternating '=' and '-'s at the edges of te message window so players can properly make sense of which message belongs to which time interval. // The '+offset%4' in the calculation makes it so that the markings scroll along with the messages. // On lines divisible by 4, draw a dark gray '-' at both horizontal extremes of the window. if( ( line + offset % 4 ) % 4 == 0 ) { mvwprintz( w, line, 1, c_dark_gray, "-" ); mvwprintz( w, line, FULL_SCREEN_WIDTH - 2, c_dark_gray, "-" ); // On lines divisible by 2 (but not 4), draw a light gray '=' at the horizontal extremes of the window. } else if( ( line + offset % 4 ) % 2 == 0 ) { mvwprintz( w, line, 1, c_light_gray, "=" ); mvwprintz( w, line, FULL_SCREEN_WIDTH - 2, c_light_gray, "=" ); } // Only now are we done with this line: line++; } } if( offset < msg_count - bottom ) { mvwprintz( w, bottom + 1, 5, c_magenta, "vvv" ); } if( offset > 0 ) { mvwprintz( w, bottom + 1, FULL_SCREEN_WIDTH - 8, c_magenta, "^^^" ); } wrefresh( w ); const std::string &action = ctxt.handle_input(); if( action == "DOWN" && offset < msg_count - bottom ) { offset++; } else if( action == "UP" && offset > 0 ) { offset--; } else if( action == "QUIT" ) { break; } } player_messages.impl_->curmes = calendar::turn.get_turn(); }