From 1724f352ea439345c2209a6ba3dfa522750a9b94 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 11 Mar 2012 16:37:08 -0400 Subject: [PATCH 01/51] Fixed moon phase check (it was accidentally setting the moon phase instead) --- calendar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calendar.cpp b/calendar.cpp index aa28eda27c..122ffcacbb 100644 --- a/calendar.cpp +++ b/calendar.cpp @@ -218,7 +218,7 @@ moon_phase calendar::moon() { int phase = day / (DAYS_IN_SEASON / 4); //phase %= 4; Redundant? - if (phase = 3) + if (phase == 3) return MOON_HALF; else return moon_phase(phase); From f0c0a0f3726a4dc83089d191a01b8d3fe279dafb Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 11 Mar 2012 17:03:47 -0400 Subject: [PATCH 02/51] Fixed various issues with Preset Character menu, including crash * The game no longer crashes if you go to the Preset Character menu with no preset characters made, then hit "Enter" * Scrolling a long list of preset characters no longer leaves the last two elements unchanged * The game correctly erases the "No templates found!" message --- game.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/game.cpp b/game.cpp index 2b7db3e364..775177b914 100644 --- a/game.cpp +++ b/game.cpp @@ -325,7 +325,7 @@ fivedozenwhales@gmail.com."); mvwprintz(w_open, 6, 12, c_red, "No templates found!"); else { int tempstart = (sel1 < 6 ? 0 : sel1 - 6), - tempend = (sel1 < 6 ? 14 : sel1 + 6); + tempend = (sel1 < 6 ? 14 : sel1 + 8); for (int i = tempstart; i < tempend; i++) { int line = 6 + i - tempstart; mvwprintz(w_open, line, 29, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); @@ -347,16 +347,16 @@ fivedozenwhales@gmail.com."); sel1++; else sel1 = 0; - } else if (ch == 'h' || ch == '<' || ch == KEY_ESCAPE) { + } else if (ch == 'h' || ch == '<' || ch == KEY_ESCAPE || templates.size() == 0) { sel1 = 1; layer = 2; - for (int i = 0; i < templates.size() && i < 21; i++) + for (int i = 0; i+6 < 21; i++) mvwprintz(w_open, 6 + i, 12, c_black, " "); for (int i = 22; i < 25; i++) mvwprintw(w_open, i, 0, " \ "); } - if (ch == 'l' || ch == '\n' || ch == '>') { + else if (ch == 'l' || ch == '\n' || ch == '>') { if (!u.create(this, PLTYPE_TEMPLATE, templates[sel1])) { u = player(); delwin(w_open); From 8688dc8d8dbe7375801ede79ad142177dd849558 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Mon, 12 Mar 2012 12:47:01 -0400 Subject: [PATCH 03/51] Fixed price_adjustment (was truncating integer division, barter skills above 6 always returned 0 price). --- skill.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skill.cpp b/skill.cpp index c959ac231e..697c9b8e72 100644 --- a/skill.cpp +++ b/skill.cpp @@ -204,6 +204,6 @@ double price_adjustment(int barter_skill) case 4: return 0.8; case 5: return 0.6; case 6: return 0.5; - default: return double(int(100 * (.3 + 1 / barter_skill)) / 100); + default: return 0.3 + 1.0 / barter_skill; } } From e0ea44a397c22e2e1909df5532a8b846b5ac3e92 Mon Sep 17 00:00:00 2001 From: Whales Date: Sun, 22 Apr 2012 22:00:05 -0400 Subject: [PATCH 04/51] Defense mode. Keybindings. --- action.cpp | 146 +++++ action.h | 66 +++ addiction.h | 2 +- artifact.cpp | 4 + catacurse.cpp | 24 +- code_doc/GAMEMODES | 121 +++++ code_doc/TODO | 71 +-- data/keymap.txt | 91 ++++ defense.cpp | 1266 ++++++++++++++++++++++++++++++++++++++++++++ field.cpp | 8 + game.cpp | 874 +++++++++++++----------------- game.h | 21 +- gamemode.cpp | 35 ++ gamemode.h | 182 +++++++ itype.h | 7 +- itypedef.cpp | 301 ++++++----- keypress.cpp | 44 ++ map.cpp | 98 +++- map.h | 5 +- mapdata.h | 39 +- mapgen.cpp | 514 +++++++++++++++++- mapitems.h | 5 +- mapitemsdef.cpp | 15 + melee.cpp | 2 +- monattack.cpp | 13 +- monattack.h | 1 + mondeath.cpp | 8 + mondeath.h | 2 + mongroup.h | 3 + mongroupdef.cpp | 15 +- monmove.cpp | 2 + monster.cpp | 32 +- mtype.h | 7 +- mtypedef.cpp | 41 +- mutation_data.cpp | 4 +- newcharacter.cpp | 8 +- omdata.h | 174 +++--- output.cpp | 45 ++ output.h | 1 + player.cpp | 63 ++- player.h | 1 + pldata.h | 4 +- ranged.cpp | 4 + setvector.cpp | 13 +- setvector.h | 2 +- trapdef.cpp | 6 +- tutorial.cpp | 222 ++++++++ tutorial.h | 25 - 48 files changed, 3702 insertions(+), 935 deletions(-) create mode 100644 action.cpp create mode 100644 action.h create mode 100644 code_doc/GAMEMODES create mode 100644 data/keymap.txt create mode 100644 defense.cpp create mode 100644 gamemode.cpp create mode 100644 gamemode.h create mode 100644 tutorial.cpp diff --git a/action.cpp b/action.cpp new file mode 100644 index 0000000000..c1031f93c4 --- /dev/null +++ b/action.cpp @@ -0,0 +1,146 @@ +#include "game.h" +#include + +action_id look_up_action(std::string ident); + +void game::load_keyboard_settings() +{ + std::ifstream fin; + fin.open("data/keymap.txt"); + while (!fin.eof()) { + std::string id; + fin >> id; + if (id == "") + getline(fin, id); // Empty line, chomp it + else if (id[0] != '#') { + action_id act = look_up_action(id); + if (act == ACTION_NULL) + debugmsg("\ +Warning! data/keymap.txt contains an unknown action, \"%s\"\n\ +Fix data/keymap.txt at your next chance!", id.c_str()); + else { + while (fin.peek() != '\n' && !fin.eof()) { + char ch; + fin >> ch; + if (keymap.find(ch) != keymap.end()) + debugmsg("\ +Warning! '%c' assigned twice in the keymap!\n\ +%s is being ignored.\n\ +Fix data/keymap.txt at your next chance!", ch, id.c_str()); + else + keymap[ ch ] = act; + } + } + } else { + getline(fin, id); // Clear the whole line + } + } +} + +action_id look_up_action(std::string ident) +{ + if (ident == "pause") + return ACTION_PAUSE; + if (ident == "move_n") + return ACTION_MOVE_N; + if (ident == "move_ne") + return ACTION_MOVE_NE; + if (ident == "move_e") + return ACTION_MOVE_E; + if (ident == "move_se") + return ACTION_MOVE_SE; + if (ident == "move_s") + return ACTION_MOVE_S; + if (ident == "move_sw") + return ACTION_MOVE_SW; + if (ident == "move_w") + return ACTION_MOVE_W; + if (ident == "move_nw") + return ACTION_MOVE_NW; + if (ident == "move_down") + return ACTION_MOVE_DOWN; + if (ident == "move_up") + return ACTION_MOVE_UP; + if (ident == "open") + return ACTION_OPEN; + if (ident == "close") + return ACTION_CLOSE; + if (ident == "smash") + return ACTION_SMASH; + if (ident == "examine") + return ACTION_EXAMINE; + if (ident == "pickup") + return ACTION_PICKUP; + if (ident == "butcher") + return ACTION_BUTCHER; + if (ident == "chat") + return ACTION_CHAT; + if (ident == "look") + return ACTION_LOOK; + if (ident == "inventory") + return ACTION_INVENTORY; + if (ident == "apply") + return ACTION_USE; + if (ident == "wear") + return ACTION_WEAR; + if (ident == "take_off") + return ACTION_TAKE_OFF; + if (ident == "eat") + return ACTION_EAT; + if (ident == "read") + return ACTION_READ; + if (ident == "wield") + return ACTION_WIELD; + if (ident == "reload") + return ACTION_RELOAD; + if (ident == "unload") + return ACTION_UNLOAD; + if (ident == "throw") + return ACTION_THROW; + if (ident == "fire") + return ACTION_FIRE; + if (ident == "fire_burst") + return ACTION_FIRE_BURST; + if (ident == "drop") + return ACTION_DROP; + if (ident == "drop_adj") + return ACTION_DIR_DROP; + if (ident == "bionics") + return ACTION_BIONICS; + if (ident == "wait") + return ACTION_WAIT; + if (ident == "craft") + return ACTION_CRAFT; + if (ident == "construct") + return ACTION_CONSTRUCT; + if (ident == "sleep") + return ACTION_SLEEP; + if (ident == "safemode") + return ACTION_TOGGLE_SAFEMODE; + if (ident == "autosafe") + return ACTION_TOGGLE_AUTOSAFE; + if (ident == "ignore_enemy") + return ACTION_IGNORE_ENEMY; + if (ident == "save") + return ACTION_SAVE; + if (ident == "quit") + return ACTION_QUIT; + if (ident == "player_data") + return ACTION_PL_INFO; + if (ident == "map") + return ACTION_MAP; + if (ident == "factions") + return ACTION_FACTIONS; + if (ident == "morale") + return ACTION_MORALE; + if (ident == "help") + return ACTION_HELP; + if (ident == "debug") + return ACTION_DEBUG; + if (ident == "debug_scent") + return ACTION_DISPLAY_SCENT; + if (ident == "debug_mon") + return ACTION_TOGGLE_DEBUGMON; + + return ACTION_NULL; +} diff --git a/action.h b/action.h new file mode 100644 index 0000000000..66b4d1245e --- /dev/null +++ b/action.h @@ -0,0 +1,66 @@ +#ifndef _ACTION_H_ +#define _ACTION_H_ + +enum action_id { +ACTION_NULL = 0, +// Movement +ACTION_PAUSE, +ACTION_MOVE_N, +ACTION_MOVE_NE, +ACTION_MOVE_E, +ACTION_MOVE_SE, +ACTION_MOVE_S, +ACTION_MOVE_SW, +ACTION_MOVE_W, +ACTION_MOVE_NW, +ACTION_MOVE_DOWN, +ACTION_MOVE_UP, +// Environment Interaction +ACTION_OPEN, +ACTION_CLOSE, +ACTION_SMASH, +ACTION_EXAMINE, +ACTION_PICKUP, +ACTION_BUTCHER, +ACTION_CHAT, +ACTION_LOOK, +// Inventory Interaction (including quasi-inventories like bionics) +ACTION_INVENTORY, +ACTION_USE, +ACTION_WEAR, +ACTION_TAKE_OFF, +ACTION_EAT, +ACTION_READ, +ACTION_WIELD, +ACTION_RELOAD, +ACTION_UNLOAD, +ACTION_THROW, +ACTION_FIRE, +ACTION_FIRE_BURST, +ACTION_DROP, +ACTION_DIR_DROP, +ACTION_BIONICS, +// Long-term / special actions +ACTION_WAIT, +ACTION_CRAFT, +ACTION_CONSTRUCT, +ACTION_SLEEP, +ACTION_TOGGLE_SAFEMODE, +ACTION_TOGGLE_AUTOSAFE, +ACTION_IGNORE_ENEMY, +ACTION_SAVE, +ACTION_QUIT, +// Info Screens +ACTION_PL_INFO, +ACTION_MAP, +ACTION_FACTIONS, +ACTION_MORALE, +ACTION_HELP, +// Debug Functions +ACTION_DEBUG, +ACTION_DISPLAY_SCENT, +ACTION_TOGGLE_DEBUGMON, +NUM_ACTIONS +}; + +#endif diff --git a/addiction.h b/addiction.h index 945fcc85d0..f9ef678166 100644 --- a/addiction.h +++ b/addiction.h @@ -103,7 +103,7 @@ void addict_effect(game *g, addiction &add) g->u.moves -= move_pen; g->u.int_cur--; g->u.str_cur--; - if (in >= 20 || int(g->turn) % (100 - in * 5) == 0) + if (g->u.stim > -100 && (in >= 20 || int(g->turn) % (100 - in * 5) == 0)) g->u.stim--; if (rng(0, 150) <= in) g->u.health--; diff --git a/artifact.cpp b/artifact.cpp index b1c7ebe941..3867eb78d1 100644 --- a/artifact.cpp +++ b/artifact.cpp @@ -64,6 +64,7 @@ It may have unknown powers; use 'a' to activate them."; // Wielded effects first while (!good_effects.empty() && !bad_effects.empty() && + num_good < 3 && num_bad < 3 && (num_good < 1 || num_bad < 1 || one_in(num_good + 1) || one_in(num_bad + 1) || value > 1)) { if (value < 1 && one_in(2)) { // Good @@ -87,6 +88,7 @@ It may have unknown powers; use 'a' to activate them."; good_effects = fill_good_passive(); bad_effects = fill_bad_passive(); while (one_in(2) && !good_effects.empty() && !bad_effects.empty() && + num_good < 3 && num_bad < 3 && ((num_good > 2 && one_in(num_good + 1)) || num_bad < 1 || one_in(num_bad + 1) || value > 1)) { if (value < 1 && one_in(3)) { // Good @@ -111,6 +113,7 @@ It may have unknown powers; use 'a' to activate them."; std::vector good_a_effects = fill_good_active(); std::vector bad_a_effects = fill_bad_active(); while (!good_a_effects.empty() && !bad_a_effects.empty() && + num_good < 3 && num_bad < 3 && ((num_bad > 0 && num_good == 0) || !one_in(3 - num_good) || !one_in(3 - num_bad))) { if (!one_in(3)) { // Good effect @@ -218,6 +221,7 @@ It may have unknown powers; use 'a' to activate them."; std::vector bad_effects = fill_bad_passive(); while (!good_effects.empty() && !bad_effects.empty() && + num_good < 3 && num_bad < 3 && (num_good < 1 || one_in(num_good * 2) || value > 1 || (num_bad < 3 && !one_in(3 - num_bad)))) { if (value < 1 && one_in(2)) { // Good effect diff --git a/catacurse.cpp b/catacurse.cpp index 8aa1324eb3..25c4855123 100644 --- a/catacurse.cpp +++ b/catacurse.cpp @@ -377,8 +377,10 @@ int delwin(WINDOW *win) delete win; return 1; }; + inline void newline(WINDOW *win){ -win->cursory++; +if (win->cursory < win->height - 1) + win->cursory++; win->cursorx=0; }; @@ -482,14 +484,18 @@ inline int printstring(WINDOW *win, char *fmt) int size = strlen(fmt); int j; for (j=0; jline[win->cursory].chars[win->cursorx]=fmt[j]; - win->line[win->cursory].FG[win->cursorx]=win->FG; - win->line[win->cursory].BG[win->cursorx]=win->BG; - win->line[win->cursory].touched=true; - addedchar(win); - } else - newline(win); // if found, make sure to move down a line + if (!(fmt[j]==10)){//check for a newline character + if (win->cursorx <= win->width - 1 && win->cursory <= win->height - 1) { + win->line[win->cursory].chars[win->cursorx]=fmt[j]; + win->line[win->cursory].FG[win->cursorx]=win->FG; + win->line[win->cursory].BG[win->cursorx]=win->BG; + win->line[win->cursory].touched=true; + addedchar(win); + } + } else if (win->cursory >= win->height - 1) + return 0; + else + newline(win); // if found, make sure to move down a line } win->draw=true; return 1; diff --git a/code_doc/GAMEMODES b/code_doc/GAMEMODES new file mode 100644 index 0000000000..1f53145bd0 --- /dev/null +++ b/code_doc/GAMEMODES @@ -0,0 +1,121 @@ +#0 Table of Contents + +#0 Table of Contents +#1 Files +#2 special_game class +#3 Functions + #3.0 id() + #3.1 init() + #3.2 per_turn() + #3.3 pre_action() + #3.4 post_action() + #3.5 game_over() + +#1 Files + +The main file to be concerned with is gamemode.h. + +This is where you can insert the class definition for your game mode, and any +enums it might use. + +gamemode.cpp is also important, if short. It contains two functions: + special_game_name() returns the name of each special game. + get_special_game() returns a pointer to a special game of the proper + type. + +Also of note are tutorial.h (houses text snippets for tutorials), and +tutorial.cpp and defense.cpp, which house code for tutorials and defense games +respectively. + +If you add a new game mode, use .cpp for its code and .h +for any static data associated with it. + + + +#2 special_game class + +Every new game mode is a child class of special_game. It may house any private +functions and variables it needs, as well as a constructor, but otherwise the +only public members should be the five functions described below. + +The game class holds a pointer to a special_game. The five functions are +called as is appropriate, and thus are cast to the proper function in a child +class. + +There is an enum, special_game_id, at the top of gamemode.h. There should be +one entry for every game mode in existance. Add one for yours. + + + +#3 Functions + + +#3.0 id() + +virtual special_game_id id(); + +Returns the special_game_id assigned to your game mode. + + +#3.1 init() + +virtual void init(game *g); + +This is run when your game mode begins. + +Some ideas: +>>Change the itype and mtype definitions--remove certain monsters from + the game, make certain items weightless, make baseballs bats uber-powerful +>>Change the recipes. Add new ones, reduce the time crafting takes. +>>Start the game in a special area. Edit the world to be entirely swamp. + + +#3.2 per_turn() + +virtual void per_turn(game *g); + +This is run every turn, before anything else is done, including player actions. + +Some ideas: +>>Reset the player's thirst, hunger, or fatigue to 0, eliminating these needs +>>Spawn some monsters every 50 turns +>>Make fire burn off faster +>>Add a chance for a space debris to fall + + +#3.3 pre_action() + +virtual void pre_action(game *g, action_id &act); + +This is run after a keypress, but before the action is performed. The action +is passed by reference, so you can modify it if you like. + +Some ideas: +>>Rewrite the functionality of actions entirely; change the long wait menu, + for example +>>Disallow certain actions by setting act to ACTION_NULL +>>Add a confirmation request to certain actions +>>Make the availability of certain actions dependant on game state + + +#3.4 post_action() + +virtual void post_action(game *g, action_id act); + +This is run after an action is performed. + +Some ideas: +>>Make certain actions perform faster by rebating movement points +>>Add extra effects to certain actions +>>Display a message upon execution of certain actions + + +#3.5 game_over() + +virtual void game_over(game *g); + +This is run when the game is over (player quit or died). + +Some ideas: +>>Add a special achievement message, e.g. number of rounds survived +>>Delete any special files created during play diff --git a/code_doc/TODO b/code_doc/TODO index 3684671b1b..8aebfab094 100644 --- a/code_doc/TODO +++ b/code_doc/TODO @@ -10,17 +10,9 @@ LONG-TERM FEATURES: Stuff I hope to finish in a year name, type, etc. Make it searchable, perhaps allow the player to define highlighted items which are always moved to the top of the list. - Finish missions and put a few simple ones in the game, get it working. -DONE Need to have a "go-to" arrow in the minimap and full map -DONE Code a "list missions" screen, and bind a key to it - - The mission_type and mission classes will almost certainly need to be - expanded to fit the needs of what we want to do. - Missions need to have save() and load() functions. - + Missions need to have save() and load() functions. - Make sure the NPC unique ID system works okay. Save the master list of NPCs - when we save the game. + Save NPCs. Once NPCs are working, populate settlements with them. Make sure that the code for placing an NPC REALLY WORKS, so that shopkeepers stay where @@ -71,16 +63,6 @@ DONE Code a "list missions" screen, and bind a key to it skills without the use of books. Maybe these XP points could be used for the above effects? - Artifacts! Both fixed ones and randomly-generated ones (a la Crawl) - For fixedarts, draw from pop culture and pop christianity; Lance of - Longinus, Ark of the Covenant, Lament Configuration, the map from - Harry Potter that shows you where people are - Randarts are normal items with a randomly chosen ability - Artifacts should have drawbacks commiserate with their power; the Sword - of Destiny is a guaranteed one-shot kill, but eventually makes your - body decay - - GENERAL: More cultural references! @@ -89,6 +71,7 @@ GENERAL: Kinda done with The Thing dog ->The whole game is already a reference to Fallout, let's admit it. So avoid Fallout references in general, they're too easy. + ->Ditto for Half-Life, the game is saturated with HL references Hellraiser: High level Void monsters could be Cenobites. Bladerunner: "Androids And The People Who Kill Them" Steam powered robots (built by post-apoc factions) @@ -114,11 +97,6 @@ GENERAL: Pushable furniture; push dressers, fridges, etc. in front of doors - Some way to protect items from spitter zombie acid, whether it be the above - or some other method. - - Unify player::miss_penalty() for moves lost when a melee attack misses - Configurable options: Keybindings Colors (e.g. highlight color, currently blue) @@ -151,25 +129,13 @@ DONE Red: Ammo for a gun you have, or vice versa Food canning. We already have tin cans, add an item "canning kit" Or make it a craft; canning requires boiling water + equipment - Random illness, e.g. cold, flu, etc. Right now we have player::get_sick() - which just modifies player::health, and the Disease Resistant trait which - does absolutely nothing. - Highlight or color-invert monsters that're fleeing; also make indifference visually obvious in some way (change glyph foreground color?) - Monster morale levels; reduced upon taking damage, upon seeing a friend die, - upon stepping close to fire if we're an animal - Audit how we're using screen space in the data window - Are pain AND painkiller level needed? - Definitely add player speed to the display Maybe make a special data structure for how the data section is used and layed out; easier to audit, player configurable -**Sharply reduce speed penalties for painkillers, and reduce stat penalties. -DONE Eliminate / increase overdose level - Make swimming more interesting Items at the bottoms of rivers, only discoverable by diving underwater Entrances to special dungeons; @@ -178,8 +144,6 @@ DONE Eliminate / increase overdose level Water-dwelling monsters. Probably rare, because for unskilled swimmers almost any monsters would be deadly - More blood & gore! Spatter blood on heavy hits, etc. - Automatically drop items if our volume capacity suddenly drops (e.g. from a torn-open backpack) @@ -192,6 +156,8 @@ DONE Eliminate / increase overdose level class for computer networks; an area on the overmap, protected by a password. Once you hack the password, you gain access to certain commands on computers found inside that area. + Make computer functions use skills other than sk_computer. For + instance, the computers in hospitals should use sk_firstaid Desires, as in The Sims 3. These could be their own class, or a special type of mission; added more or less randomly, they include things like "eat an @@ -231,10 +197,6 @@ CHARACTER CREATION: Greater transparency for skill comprehension (at level 0, 4, 8, etc.) -**Cap the number of traits a character can have (5 good, 5 bad?) - - Change "Heavy Sleeper costs -1 points" to "Heavy Sleeper earns 1 point" - Show food consumption rate? (Modified by Light Eater) Should high Strength increase this rate? @@ -265,35 +227,12 @@ TRAITS: Balance/fix/remove Jittery - Balance/fix/remove Hoarder (it's too annoying) - Maybe you just can't drop stuff unless you're over 80% volume/weight? - Xanax providing a temporary cure should definitely stay. - More obvious Wool Allergy text (remind the player that they're allergic) Wool Allergy prevents eating wool (with Internal Furnace bionic) -**Parkour Expert halves all terrain costs (including windows, etc) - Increase point cost? - Decrease chance of being hurt on rough/sharp terrain? - Note movement cost change when using ';' command? - - Increase Night Vision radius to 3? - - Re-examine Thick-Skinned; make it more effective? - Make Fast Learner affect skill comprehension directly Rewrite description - Fold Deft into unified player::miss_penalty() - - Review the code for Animal Empathy; I think it needs to be unified somehow. - - Likewise for Terrifying. Maybe a single monster::opinion_of_you() that - returns aggressive, afraid, indifferent or friend? - - High Adrenaline needs a little balancing. A special note in the data area - might be a good idea; "Adr. Rush" or "Adr. Comedown" - Android encourages start-scumming. Maybe make it a static set, or present the player with a few different options? Make Android a class instead? (see above under LONG-TERM) diff --git a/data/keymap.txt b/data/keymap.txt new file mode 100644 index 0000000000..9a5e3169fc --- /dev/null +++ b/data/keymap.txt @@ -0,0 +1,91 @@ +# This is the keymapping for Cataclysm. +# You can start a line with # to make it a comment--it will be ignored. +# Blank lines are ignored too. +# Extra whitespace, including tabs, is ignored, so format things how you like. + +# The format for each line is an action identifier, followed by several +# keys. Any action may have an unlimited number of keys bound to it. +# If you bind the same key to multiple actions, the second and subsequent +# bindings will be ignored--and you'll get a warning when the game starts. +# Keys are case-sensitive, of course; c and C are different. + +# WARNING: If you skip an action identifier, there will be no key bound to +# that action! You will be NOT be warned of this when the game starts. +# If you're going to mess with stuff, maybe you should back this file up? + +# It is okay to split commands across lines. +# pause . 5 is equivalent to: +# pause . +# pause 5 + +# Note that movement keybindings ONLY apply to movement (for now). +# That is, binding w to move_n will let you use w to move north, but you +# cannot use w to smash north, examine to the north, etc. +# For now, you must use vikeys, the numpad, or arrow keys for those actions. +# This is planned to change in the future. + +# Finally, there is no support for special keys, like tab, spacebar, Home, and +# so on. This is not a planned feature, but if it's important to you, please +# let me know. + +# MOVEMENT: +pause . 5 +move_n k 8 +move_ne u 9 +move_e l 6 +move_se n 3 +move_s j 2 +move_sw b 1 +move_w h 4 +move_nw y 7 +move_down > +move_up < + +# ENVIRONMENT INTERACTION +open o +close c +smash s +examine e +pickup , g +butcher B +chat C +look ; x + +# INVENTORY & QUASI-INVENTORY INTERACTION +inventory i +apply a +wear W +take_off T +eat E +read R +wield w +reload r +unload U +throw t +fire f +fire_burst F +drop d +drop_adj D +bionics p + +# LONG TERM & SPECIAL ACTIONS +wait ^ +craft & +construct * +sleep $ +safemode ! +autosafe " +ignore_enemy ' +save S +quit Q + +# INFO SCREENS +player_data @ +map m : +factions # +morale % + +# DEBUG FUNCTIONS +debug Z +debug_scent - +debug_mon ~ diff --git a/defense.cpp b/defense.cpp new file mode 100644 index 0000000000..17e234a034 --- /dev/null +++ b/defense.cpp @@ -0,0 +1,1266 @@ +#include "gamemode.h" +#include "game.h" +#include "setvector.h" +#include "keypress.h" +#include "itype.h" +#include "mtype.h" +#include +#include +#include + +#define SPECIAL_WAVE_CHANCE 5 // One in X chance of single-flavor wave +#define SPECIAL_WAVE_MIN 5 // Don't use a special wave with < X monsters + +#define SELCOL(n) (selection == (n) ? c_yellow : c_blue) +#define TOGCOL(n, b) (selection == (n) ? (b ? c_ltgreen : c_yellow) :\ + (b ? c_green : c_dkgray)) +#define NUMALIGN(n) ((n) >= 10000 ? 20 : ((n) >= 1000 ? 21 :\ + ((n) >= 100 ? 22 : ((n) >= 10 ? 23 : 24)))) + +std::string caravan_category_name(caravan_category cat); +std::vector caravan_items(caravan_category cat); + +int caravan_price(player &u, int price); + +void draw_caravan_borders(WINDOW *w, int current_window); +void draw_caravan_categories(WINDOW *w, int category_selected, int total_price, + int cash); +void draw_caravan_items(WINDOW *w, game *g, std::vector *items, + std::vector *counts, int offset, + int item_selected); + +std::string defense_style_name(defense_style style); +std::string defense_style_description(defense_style style); +std::string defense_location_name(defense_location location); +std::string defense_location_description(defense_location location); + +defense_game::defense_game() +{ + current_wave = 0; + hunger = false; + thirst = false; + sleep = false; + zombies = false; + specials = false; + spiders = false; + triffids = false; + robots = false; + subspace = false; + mercenaries = false; + init_to_style(DEFENSE_EASY); +} + +void defense_game::init(game *g) +{ + g->turn = HOURS(12); // Start at noon + g->temperature = 65; + g->u.create(g, PLTYPE_CUSTOM); + g->u.str_cur = g->u.str_max; + g->u.per_cur = g->u.per_max; + g->u.int_cur = g->u.int_max; + g->u.dex_cur = g->u.dex_max; + init_itypes(g); + init_mtypes(g); + init_constructions(g); + init_recipes(g); + current_wave = 0; + hunger = false; + thirst = false; + sleep = false; + zombies = false; + specials = false; + spiders = false; + triffids = false; + robots = false; + subspace = false; + mercenaries = false; + init_to_style(DEFENSE_EASY); + setup(); + g->u.cash = initial_cash; + popup_nowait("Please wait as the map generates [ 0%]"); + init_map(g); + caravan(g); +} + +void defense_game::per_turn(game *g) +{ + if (!thirst) + g->u.thirst = 0; + if (!hunger) + g->u.hunger = 0; + if (!sleep) + g->u.fatigue = 0; + if (int(g->turn) % (time_between_waves * 10) == 0) { + current_wave++; + if (current_wave > 1 && current_wave % waves_between_caravans == 0) { + popup("A caravan approaches! Press spacebar..."); + caravan(g); + } + spawn_wave(g); + } +} + +void defense_game::pre_action(game *g, action_id &act) +{ + if (act == ACTION_SLEEP && !sleep) { + g->add_msg("You don't need to sleep!"); + act = ACTION_NULL; + } + if (act == ACTION_SAVE) { + g->add_msg("You cannot save in defense mode!"); + act = ACTION_NULL; + } +} + +void defense_game::post_action(game *g, action_id act) +{ +} + +void defense_game::game_over(game *g) +{ + popup("You managed to survive through wave %d!", current_wave); +} + +void defense_game::init_itypes(game *g) +{ + g->itypes[itm_2x4]->volume = 0; + g->itypes[itm_2x4]->weight = 0; + g->itypes[itm_landmine]->price = 300; + g->itypes[itm_bot_turret]->price = 6000; +} + +void defense_game::init_mtypes(game *g) +{ + for (int i = 0; i < num_monsters; i++) { + g->mtypes[i]->difficulty *= 1.5; + g->mtypes[i]->difficulty += int(g->mtypes[i]->difficulty / 5); + g->mtypes[i]->flags.push_back(MF_BASHES); + g->mtypes[i]->flags.push_back(MF_SMELLS); + g->mtypes[i]->flags.push_back(MF_HEARS); + g->mtypes[i]->flags.push_back(MF_SEES); + } +} + +void defense_game::init_constructions(game *g) +{ + for (int i = 0; i < g->constructions.size(); i++) { + for (int j = 0; j < g->constructions[i]->stages.size(); j++) { + g->constructions[i]->stages[j].time = 1; // Everything takes 1 minute + } + } +} + +void defense_game::init_recipes(game *g) +{ + for (int i = 0; i < g->recipes.size(); i++) { + g->recipes[i]->time /= 10; // Things take turns, not minutes + } +} + +void defense_game::init_map(game *g) +{ + for (int x = 0; x < OMAPX; x++) { + for (int y = 0; y < OMAPY; y++) { + g->cur_om.ter(x, y) = ot_field; + g->cur_om.seen(x, y) = true; + } + } + + g->cur_om.save(g->u.name, 0, 0, DEFENSE_Z); + g->levx = 100; + g->levy = 100; + g->levz = 0; + g->u.posx = SEEX; + g->u.posy = SEEY; + + switch (location) { + + case DEFLOC_HOSPITAL: + for (int x = 49; x <= 51; x++) { + for (int y = 49; y <= 51; y++) + g->cur_om.ter(x, y) = ot_hospital; + } + g->cur_om.ter(50, 49) = ot_hospital_entrance; + break; + + case DEFLOC_MALL: + for (int x = 49; x <= 51; x++) { + for (int y = 49; y <= 51; y++) + g->cur_om.ter(x, y) = ot_megastore; + } + g->cur_om.ter(50, 49) = ot_megastore_entrance; + break; + + case DEFLOC_BAR: + g->cur_om.ter(50, 50) = ot_bar_north; + break; + + case DEFLOC_MANSION: + for (int x = 49; x <= 51; x++) { + for (int y = 49; y <= 51; y++) + g->cur_om.ter(x, y) = ot_mansion; + } + g->cur_om.ter(50, 49) = ot_mansion_entrance; + break; + } +// Init the map + int old_percent = 0; + for (int i = 0; i <= MAPSIZE * 2; i += 2) { + for (int j = 0; j <= MAPSIZE * 2; j += 2) { + int mx = g->levx - MAPSIZE + i, my = g->levy - MAPSIZE + j; + int percent = 100 * ((j / 2 + MAPSIZE * (i / 2))) / + ((MAPSIZE + 1) * (MAPSIZE + 1)); + if (percent >= old_percent + 1) { + popup_nowait("Please wait as the map generates [%s%d%]", + (percent < 10 ? " " : ""), percent); + old_percent = percent; + } +// Round down to the nearest even number + mx -= mx % 2; + my -= my % 2; + tinymap tm(&g->itypes, &g->mapitems, &g->traps); + tm.generate(g, &(g->cur_om), mx, my, int(g->turn)); + tm.clear_spawns(); + tm.clear_traps(); + tm.save(&g->cur_om, int(g->turn), mx, my); + } + } + + g->m.load(g, g->levx, g->levy); + + g->update_map(g->u.posx, g->u.posy); + monster generator(g->mtypes[mon_generator], g->u.posx + 1, g->u.posy + 1); + generator.friendly = -1; + g->z.push_back(generator); +} + +void defense_game::init_to_style(defense_style new_style) +{ + style = new_style; + hunger = false; + thirst = false; + sleep = false; + zombies = false; + specials = false; + spiders = false; + triffids = false; + robots = false; + subspace = false; + mercenaries = false; + + switch (new_style) { + case DEFENSE_EASY: + location = DEFLOC_HOSPITAL; + initial_difficulty = 15; + wave_difficulty = 10; + time_between_waves = 30; + waves_between_caravans = 3; + initial_cash = 10000; + cash_per_wave = 1000; + cash_increase = 300; + specials = true; + spiders = true; + triffids = true; + mercenaries = true; + break; + + case DEFENSE_MEDIUM: + location = DEFLOC_MALL; + initial_difficulty = 30; + wave_difficulty = 15; + time_between_waves = 20; + waves_between_caravans = 4; + initial_cash = 6000; + cash_per_wave = 800; + cash_increase = 200; + specials = true; + spiders = true; + triffids = true; + robots = true; + hunger = true; + mercenaries = true; + break; + + case DEFENSE_HARD: + location = DEFLOC_BAR; + initial_difficulty = 50; + wave_difficulty = 20; + time_between_waves = 10; + waves_between_caravans = 5; + initial_cash = 2000; + cash_per_wave = 600; + cash_increase = 100; + specials = true; + spiders = true; + triffids = true; + robots = true; + subspace = true; + hunger = true; + thirst = true; + break; + + break; + + case DEFENSE_SHAUN: + location = DEFLOC_BAR; + initial_difficulty = 30; + wave_difficulty = 15; + time_between_waves = 5; + waves_between_caravans = 6; + initial_cash = 5000; + cash_per_wave = 500; + cash_increase = 100; + zombies = true; + break; + + case DEFENSE_DAWN: + location = DEFLOC_MALL; + initial_difficulty = 60; + wave_difficulty = 20; + time_between_waves = 30; + waves_between_caravans = 4; + initial_cash = 8000; + cash_per_wave = 500; + cash_increase = 0; + zombies = true; + hunger = true; + thirst = true; + mercenaries = true; + break; + + case DEFENSE_SPIDERS: + location = DEFLOC_MALL; + initial_difficulty = 60; + wave_difficulty = 10; + time_between_waves = 10; + waves_between_caravans = 4; + initial_cash = 6000; + cash_per_wave = 500; + cash_increase = 100; + spiders = true; + break; + + case DEFENSE_TRIFFIDS: + location = DEFLOC_MANSION; + initial_difficulty = 60; + wave_difficulty = 20; + time_between_waves = 30; + waves_between_caravans = 2; + initial_cash = 10000; + cash_per_wave = 600; + cash_increase = 100; + triffids = true; + hunger = true; + thirst = true; + sleep = true; + mercenaries = true; + break; + + case DEFENSE_SKYNET: + location = DEFLOC_HOSPITAL; + initial_difficulty = 20; + wave_difficulty = 20; + time_between_waves = 20; + waves_between_caravans = 6; + initial_cash = 12000; + cash_per_wave = 1000; + cash_increase = 200; + robots = true; + hunger = true; + thirst = true; + mercenaries = true; + break; + + case DEFENSE_LOVECRAFT: + location = DEFLOC_MANSION; + initial_difficulty = 20; + wave_difficulty = 20; + time_between_waves = 120; + waves_between_caravans = 8; + initial_cash = 4000; + cash_per_wave = 1000; + cash_increase = 100; + subspace = true; + hunger = true; + thirst = true; + sleep = true; + } +} + +void defense_game::setup() +{ + WINDOW* w = newwin(25, 80, 0, 0); + int selection = 1; + refresh_setup(w, selection); + + while (true) { + char ch = input(); + + if (ch == 'S') { + if (!zombies && !specials && !spiders && !triffids && !robots && !subspace) { + popup("You must choose at least one monster group!"); + refresh_setup(w, selection); + } else + return; + } else if (ch == '+' || ch == '>' || ch == 'j') { + if (selection == 19) + selection = 1; + else + selection++; + refresh_setup(w, selection); + } else if (ch == '-' || ch == '<' || ch == 'k') { + if (selection == 1) + selection = 19; + else + selection--; + refresh_setup(w, selection); + } else if (ch == '!') { + std::string name = string_input_popup(20, "Template Name:"); + refresh_setup(w, selection); + } else if (ch == 'S') + return; + + else { + switch (selection) { + case 1: // Scenario selection + if (ch == 'l') { + if (style == defense_style(NUM_DEFENSE_STYLES - 1)) + style = defense_style(1); + else + style = defense_style(style + 1); + } + if (ch == 'h') { + if (style == defense_style(1)) + style = defense_style(NUM_DEFENSE_STYLES - 1); + else + style = defense_style(style - 1); + } + init_to_style(style); + break; + + case 2: // Location selection + if (ch == 'l') { + if (location == defense_location(NUM_DEFENSE_LOCATIONS - 1)) + location = defense_location(1); + else + location = defense_location(location + 1); + } + if (ch == 'h') { + if (location == defense_location(1)) + location = defense_location(NUM_DEFENSE_LOCATIONS - 1); + else + location = defense_location(location - 1); + } + mvwprintz(w, 5, 2, c_black, "\ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + mvwprintz(w, 5, 2, c_yellow, defense_location_name(location).c_str()); + mvwprintz(w, 5, 28, c_ltgray, + defense_location_description(location).c_str()); + break; + + case 3: // Difficulty of the first wave + if (ch == 'h' && initial_difficulty > 10) + initial_difficulty -= 5; + if (ch == 'l' && initial_difficulty < 995) + initial_difficulty += 5; + mvwprintz(w, 7, 22, c_black, "xxx"); + mvwprintz(w, 7, NUMALIGN(initial_difficulty), c_yellow, "%d", + initial_difficulty); + break; + + case 4: // Wave Difficulty + if (ch == 'h' && wave_difficulty > 10) + wave_difficulty -= 5; + if (ch == 'l' && wave_difficulty < 995) + wave_difficulty += 5; + mvwprintz(w, 8, 22, c_black, "xxx"); + mvwprintz(w, 8, NUMALIGN(wave_difficulty), c_yellow, "%d", + wave_difficulty); + break; + + case 5: + if (ch == 'h' && time_between_waves > 5) + time_between_waves -= 5; + if (ch == 'l' && time_between_waves < 995) + time_between_waves += 5; + mvwprintz(w, 10, 22, c_black, "xxx"); + mvwprintz(w, 10, NUMALIGN(time_between_waves), c_yellow, "%d", + time_between_waves); + break; + + case 6: + if (ch == 'h' && waves_between_caravans > 1) + waves_between_caravans -= 1; + if (ch == 'l' && waves_between_caravans < 50) + waves_between_caravans += 1; + mvwprintz(w, 11, 22, c_black, "xxx"); + mvwprintz(w, 11, NUMALIGN(waves_between_caravans), c_yellow, "%d", + waves_between_caravans); + break; + + case 7: + if (ch == 'h' && initial_cash > 0) + initial_cash -= 100; + if (ch == 'l' && initial_cash < 99900) + initial_cash += 100; + mvwprintz(w, 13, 20, c_black, "xxxxx"); + mvwprintz(w, 13, NUMALIGN(initial_cash), c_yellow, "%d", initial_cash); + break; + + case 8: + if (ch == 'h' && cash_per_wave > 0) + cash_per_wave -= 100; + if (ch == 'l' && cash_per_wave < 9900) + cash_per_wave += 100; + mvwprintz(w, 14, 21, c_black, "xxxx"); + mvwprintz(w, 14, NUMALIGN(cash_per_wave), c_yellow, "%d", cash_per_wave); + break; + + case 9: + if (ch == 'h' && cash_increase > 0) + cash_increase -= 50; + if (ch == 'l' && cash_increase < 9950) + cash_increase += 50; + mvwprintz(w, 15, 21, c_black, "xxxx"); + mvwprintz(w, 15, NUMALIGN(cash_increase), c_yellow, "%d", cash_increase); + break; + + case 10: + if (ch == ' ' || ch == '\n') { + zombies = !zombies; + specials = false; + } + mvwprintz(w, 18, 2, (zombies ? c_ltgreen : c_yellow), "Zombies"); + mvwprintz(w, 18, 14, c_yellow, "Special Zombies"); + break; + + case 11: + if (ch == ' ' || ch == '\n') { + specials = !specials; + zombies = false; + } + mvwprintz(w, 18, 2, c_yellow, "Zombies"); + mvwprintz(w, 18, 14, (specials ? c_ltgreen : c_yellow), "Special Zombies"); + break; + + case 12: + if (ch == ' ' || ch == '\n') + spiders = !spiders; + mvwprintz(w, 18, 34, (spiders ? c_ltgreen : c_yellow), "Spiders"); + break; + + case 13: + if (ch == ' ' || ch == '\n') + triffids = !triffids; + mvwprintz(w, 18, 46, (triffids ? c_ltgreen : c_yellow), "Triffids"); + break; + + case 14: + if (ch == ' ' || ch == '\n') + robots = !robots; + mvwprintz(w, 18, 59, (robots ? c_ltgreen : c_yellow), "Robots"); + break; + + case 15: + if (ch == ' ' || ch == '\n') + subspace = !subspace; + mvwprintz(w, 18, 70, (subspace ? c_ltgreen : c_yellow), "Subspace"); + break; + + case 16: + if (ch == ' ' || ch == '\n') + hunger = !hunger; + mvwprintz(w, 21, 2, (hunger ? c_ltgreen : c_yellow), "Food"); + break; + + case 17: + if (ch == ' ' || ch == '\n') + thirst = !thirst; + mvwprintz(w, 21, 16, (thirst ? c_ltgreen : c_yellow), "Water"); + break; + + case 18: + if (ch == ' ' || ch == '\n') + sleep = !sleep; + mvwprintz(w, 21, 31, (sleep ? c_ltgreen : c_yellow), "Sleep"); + break; + + case 19: + if (ch == ' ' || ch == '\n') + mercenaries = !mercenaries; + mvwprintz(w, 21, 46, (mercenaries ? c_ltgreen : c_yellow), "Mercenaries"); + break; + } + } + if (ch == 'h' || ch == 'l' || ch == ' ' || ch == '\n') + refresh_setup(w, selection); + } +} + +void defense_game::refresh_setup(WINDOW* w, int selection) +{ + werase(w); + mvwprintz(w, 0, 1, c_ltred, "DEFENSE MODE"); + mvwprintz(w, 0, 28, c_ltred, "Press +/- or >/< to cycle, spacebar to toggle"); + mvwprintz(w, 1, 28, c_ltred, "Press S to start, ! to save as a template"); + mvwprintz(w, 2, 2, c_ltgray, "Scenario:"); + mvwprintz(w, 3, 2, SELCOL(1), defense_style_name(style).c_str()); + mvwprintz(w, 3, 28, c_ltgray, defense_style_description(style).c_str()); + mvwprintz(w, 4, 2, c_ltgray, "Location:"); + mvwprintz(w, 5, 2, SELCOL(2), defense_location_name(location).c_str()); + mvwprintz(w, 5, 28, c_ltgray, defense_location_description(location).c_str()); + + mvwprintz(w, 7, 2, c_ltgray, "Initial Difficulty:"); + mvwprintz(w, 7, NUMALIGN(initial_difficulty), SELCOL(3), "%d", + initial_difficulty); + mvwprintz(w, 7, 28, c_ltgray, "The difficulty of the first wave."); + mvwprintz(w, 8, 2, c_ltgray, "Wave Difficulty:"); + mvwprintz(w, 8, NUMALIGN(wave_difficulty), SELCOL(4), "%d", wave_difficulty); + mvwprintz(w, 8, 28, c_ltgray, "The increase of difficulty with each wave."); + + mvwprintz(w, 10, 2, c_ltgray, "Time b/w Waves:"); + mvwprintz(w, 10, NUMALIGN(time_between_waves), SELCOL(5), "%d", + time_between_waves); + mvwprintz(w, 10, 28, c_ltgray, "The time, in minutes, between waves."); + mvwprintz(w, 11, 2, c_ltgray, "Waves b/w Caravans:"); + mvwprintz(w, 11, NUMALIGN(waves_between_caravans), SELCOL(6), "%d", + waves_between_caravans); + mvwprintz(w, 11, 28, c_ltgray, "The number of waves in between caravans."); + + mvwprintz(w, 13, 2, c_ltgray, "Initial Cash:"); + mvwprintz(w, 13, NUMALIGN(initial_cash), SELCOL(7), "%d", initial_cash); + mvwprintz(w, 13, 28, c_ltgray, "The amount of money the player starts with."); + mvwprintz(w, 14, 2, c_ltgray, "Cash for 1st Wave:"); + mvwprintz(w, 14, NUMALIGN(cash_per_wave), SELCOL(8), "%d", cash_per_wave); + mvwprintz(w, 14, 28, c_ltgray, "The cash awarded for the first wave."); + mvwprintz(w, 15, 2, c_ltgray, "Cash Increase:"); + mvwprintz(w, 15, NUMALIGN(cash_increase), SELCOL(9), "%d", cash_increase); + mvwprintz(w, 15, 28, c_ltgray, "The increase in the award each wave."); + + mvwprintz(w, 17, 2, c_ltgray, "Enemy Selection:"); + mvwprintz(w, 18, 2, TOGCOL(10, zombies), "Zombies"); + mvwprintz(w, 18, 14, TOGCOL(11, specials), "Special Zombies"); + mvwprintz(w, 18, 34, TOGCOL(12, spiders), "Spiders"); + mvwprintz(w, 18, 46, TOGCOL(13, triffids), "Triffids"); + mvwprintz(w, 18, 59, TOGCOL(14, robots), "Robots"); + mvwprintz(w, 18, 70, TOGCOL(15, subspace), "Subspace"); + + mvwprintz(w, 20, 2, c_ltgray, "Needs:"); + mvwprintz(w, 21, 2, TOGCOL(16, hunger), "Food"); + mvwprintz(w, 21, 16, TOGCOL(17, thirst), "Water"); + mvwprintz(w, 21, 31, TOGCOL(18, sleep), "Sleep"); + mvwprintz(w, 21, 46, TOGCOL(19, mercenaries), "Mercenaries"); + wrefresh(w); +} + +std::string defense_style_name(defense_style style) +{ +// 24 Characters Max! + switch (style) { + case DEFENSE_CUSTOM: return "Custom"; + case DEFENSE_EASY: return "Easy"; + case DEFENSE_MEDIUM: return "Medium"; + case DEFENSE_HARD: return "Hard"; + case DEFENSE_SHAUN: return "Shaun of the Dead"; + case DEFENSE_DAWN: return "Dawn of the Dead"; + case DEFENSE_SPIDERS: return "Eight-Legged Freaks"; + case DEFENSE_TRIFFIDS: return "Day of the Triffids"; + case DEFENSE_SKYNET: return "Skynet"; + case DEFENSE_LOVECRAFT: return "The Call of Cthulhu"; + default: return "Bug! Report this!"; + } +} + +std::string defense_style_description(defense_style style) +{ +// 51 Characters Max! + switch (style) { + case DEFENSE_CUSTOM: + return "A custom game."; + case DEFENSE_EASY: + return "Easy monsters and lots of money."; + case DEFENSE_MEDIUM: + return "Harder monsters. You have to eat."; + case DEFENSE_HARD: + return "All monsters. You have to eat and drink."; + case DEFENSE_SHAUN: + return "Defend a bar against classic zombies. Easy and fun."; + case DEFENSE_DAWN: + return "Classic zombies. Slower and more realistic."; + case DEFENSE_SPIDERS: + return "Fast-paced spider-fighting fun!"; + case DEFENSE_TRIFFIDS: + return "Defend your mansion against the triffids."; + case DEFENSE_SKYNET: + return "The robots have decided that humans are the enemy!"; + case DEFENSE_LOVECRAFT: + return "Ward off legions of eldritch horrors."; + default: + return "What the heck is this I don't even know. A bug!"; + } +} + +std::string defense_location_name(defense_location location) +{ + switch (location) { + case DEFLOC_NULL: return "Nowhere?! A bug!"; + case DEFLOC_HOSPITAL: return "Hospital"; + case DEFLOC_MALL: return "Megastore"; + case DEFLOC_BAR: return "Bar"; + case DEFLOC_MANSION: return "Mansion"; + default: return "a ghost's house (bug)"; + } +} + +std::string defense_location_description(defense_location location) +{ + switch (location) { + case DEFLOC_NULL: + return "NULL Bug."; + case DEFLOC_HOSPITAL: + return "One entrance and many rooms. Some medical supplies."; + case DEFLOC_MALL: + return "A large building with various supplies."; + case DEFLOC_BAR: + return "A small building with plenty of alcohol."; + case DEFLOC_MANSION: + return "A large house with many rooms and."; + default: + return "Unknown data bug."; + } +} + +void defense_game::caravan(game *g) +{ + caravan_category tab = CARAVAN_MELEE; + std::vector items[NUM_CARAVAN_CATEGORIES]; + std::vector item_count[NUM_CARAVAN_CATEGORIES]; + +// Init the items for each category + for (int i = 0; i < NUM_CARAVAN_CATEGORIES; i++) { + items[i] = caravan_items( caravan_category(i) ); + for (int j = 0; j < items[i].size(); j++) { + if (current_wave == 0 || !one_in(4)) + item_count[i].push_back(0); // Init counts to 0 for each item + else { // Remove the item + items[i].erase( items[i].begin() + j); + j--; + } + } + } + + int total_price = 0; + + WINDOW *w = newwin(25, 80, 0, 0); + + int offset = 0, item_selected = 0, category_selected = 0; + + int current_window = 0; + + draw_caravan_borders(w, current_window); + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + + bool done = false; + bool cancel = false; + while (!done) { + + char ch = input(); + switch (ch) { + case '?': + popup_top("\ +CARAVAN:\n\ +Start by selecting a category using your favorite up/down keys.\n\ +Switch between category selection and item selecting by pressing Tab.\n\ +Pick an item with the up/down keys, press + to buy 1 more, - to buy 1 less.\n\ +Press Enter to buy everything in your cart, Esc to buy nothing."); + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, item_selected); + draw_caravan_borders(w, current_window); + break; + + case 'j': + if (current_window == 0) { // Categories + category_selected++; + if (category_selected == NUM_CARAVAN_CATEGORIES) + category_selected = CARAVAN_CART; + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + offset = 0; + item_selected = 0; + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, + item_selected); + draw_caravan_borders(w, current_window); + } else if (items[category_selected].size() > 0) { // Items + if (item_selected < items[category_selected].size() - 1) + item_selected++; + else { + item_selected = 0; + offset = 0; + } + if (item_selected > offset + 22) + offset++; + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, + item_selected); + draw_caravan_borders(w, current_window); + } + break; + + case 'k': + if (current_window == 0) { // Categories + if (category_selected == 0) + category_selected = NUM_CARAVAN_CATEGORIES - 1; + else + category_selected--; + if (category_selected == NUM_CARAVAN_CATEGORIES) + category_selected = CARAVAN_CART; + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + offset = 0; + item_selected = 0; + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, + item_selected); + draw_caravan_borders(w, current_window); + } else if (items[category_selected].size() > 0) { // Items + if (item_selected > 0) + item_selected--; + else { + item_selected = items[category_selected].size() - 1; + offset = item_selected - 22; + if (offset < 0) + offset = 0; + } + if (item_selected < offset) + offset--; + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, + item_selected); + draw_caravan_borders(w, current_window); + } + break; + + case '+': + case 'l': + if (current_window == 1 && items[category_selected].size() > 0) { + item_count[category_selected][item_selected]++; + itype_id tmp_itm = items[category_selected][item_selected]; + total_price += caravan_price(g->u, g->itypes[tmp_itm]->price); + if (category_selected == CARAVAN_CART) { // Find the item in its category + for (int i = 1; i < NUM_CARAVAN_CATEGORIES; i++) { + for (int j = 0; j < items[i].size(); j++) { + if (items[i][j] == tmp_itm) + item_count[i][j]++; + } + } + } else { // Add / increase the item in the shopping cart + bool found_item = false; + for (int i = 0; i < items[0].size() && !found_item; i++) { + if (items[0][i] == tmp_itm) { + found_item = true; + item_count[0][i]++; + } + } + if (!found_item) { + items[0].push_back(items[category_selected][item_selected]); + item_count[0].push_back(1); + } + } + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, item_selected); + draw_caravan_borders(w, current_window); + } + break; + + case '-': + case 'h': + if (current_window == 1 && items[category_selected].size() > 0 && + item_count[category_selected][item_selected] > 0) { + item_count[category_selected][item_selected]--; + itype_id tmp_itm = items[category_selected][item_selected]; + total_price -= caravan_price(g->u, g->itypes[tmp_itm]->price); + if (category_selected == CARAVAN_CART) { // Find the item in its category + for (int i = 1; i < NUM_CARAVAN_CATEGORIES; i++) { + for (int j = 0; j < items[i].size(); j++) { + if (items[i][j] == tmp_itm) + item_count[i][j]--; + } + } + } else { // Decrease / remove the item in the shopping cart + bool found_item = false; + for (int i = 0; i < items[0].size() && !found_item; i++) { + if (items[0][i] == tmp_itm) { + found_item = true; + item_count[0][i]--; + if (item_count[0][i] == 0) { + item_count[0].erase(item_count[0].begin() + i); + items[0].erase(items[0].begin() + i); + } + } + } + } + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, item_selected); + draw_caravan_borders(w, current_window); + } + break; + + case '\t': + current_window = (current_window + 1) % 2; + draw_caravan_borders(w, current_window); + break; + + case KEY_ESCAPE: + if (query_yn("Really buy nothing?")) { + cancel = true; + done = true; + } else { + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, item_selected); + draw_caravan_borders(w, current_window); + } + break; + + case '\n': + if (total_price > g->u.cash) + popup("You can't afford those items!"); + else if ((items[0].empty() && query_yn("Really buy nothing?")) || + (!items[0].empty() && + query_yn("Buy %d items, leaving you with $%d?", items[0].size(), + g->u.cash - total_price))) + done = true; + if (!done) { // We canceled, so redraw everything + draw_caravan_categories(w, category_selected, total_price, g->u.cash); + draw_caravan_items(w, g, &(items[category_selected]), + &(item_count[category_selected]), offset, item_selected); + draw_caravan_borders(w, current_window); + } + break; + } // switch (ch) + + } // while (!done) + + if (!cancel) { + g->u.cash -= total_price; + bool dropped_some = false; + for (int i = 0; i < items[0].size(); i++) { + item tmp(g->itypes[ items[0][i] ], g->turn); + tmp = tmp.in_its_container(&(g->itypes)); + for (int j = 0; j < item_count[0][i]; j++) { + if (g->u.volume_carried() + tmp.volume() <= g->u.volume_capacity() && + g->u.weight_carried() + tmp.weight() <= g->u.weight_capacity() && + g->u.inv.size() < 52) + g->u.i_add(tmp); + else { // Could fit it in the inventory! + dropped_some = true; + g->m.add_item(g->u.posx, g->u.posy, tmp); + } + } + } + if (dropped_some) + g->add_msg("You drop some items."); + } +} + +std::string caravan_category_name(caravan_category cat) +{ + switch (cat) { + case CARAVAN_CART: return "Shopping Cart"; + case CARAVAN_MELEE: return "Melee Weapons"; + case CARAVAN_GUNS: return "Firearms & Ammo"; + case CARAVAN_COMPONENTS: return "Crafting & Construction Components"; + case CARAVAN_FOOD: return "Food & Drugs"; + case CARAVAN_CLOTHES: return "Clothing & Armor"; + case CARAVAN_TOOLS: return "Tools, Traps & Grenades"; + } + return "BUG"; +} + +std::vector caravan_items(caravan_category cat) +{ + std::vector ret; + switch (cat) { + case CARAVAN_CART: + return ret; + + case CARAVAN_MELEE: + setvector(ret, +itm_hammer, itm_bat, itm_mace, itm_morningstar, itm_hammer_sledge, itm_hatchet, +itm_knife_combat, itm_rapier, itm_machete, itm_katana, itm_spear_knife, +itm_pike, itm_chainsaw_off, NULL); + break; + + case CARAVAN_GUNS: + setvector(ret, +itm_crossbow, itm_bolt_steel, itm_compbow, itm_arrow_cf, itm_marlin_9a, +itm_22_lr, itm_hk_mp5, itm_9mm, itm_taurus_38, itm_38_special, itm_deagle_44, +itm_44magnum, itm_m1911, itm_hk_ump45, itm_45_acp, itm_fn_p90, itm_57mm, +itm_remington_870, itm_shot_00, itm_shot_slug, itm_browning_blr, itm_3006, +itm_ak47, itm_762_m87, itm_m4a1, itm_556, itm_savage_111f, itm_hk_g3, +itm_762_51, itm_hk_g80, itm_12mm, itm_plasma_rifle, itm_plasma, NULL); + break; + + case CARAVAN_COMPONENTS: + setvector(ret, +itm_rag, itm_fur, itm_leather, itm_superglue, itm_string_36, itm_chain, +itm_processor, itm_RAM, itm_power_supply, itm_motor, itm_hose, itm_pot, +itm_2x4, itm_battery, itm_nail, itm_gasoline, NULL); + break; + + case CARAVAN_FOOD: + setvector(ret, +itm_1st_aid, itm_water, itm_energy_drink, itm_whiskey, itm_can_beans, +itm_mre_beef, itm_flour, itm_inhaler, itm_codeine, itm_oxycodone, itm_adderall, +itm_cig, itm_meth, itm_royal_jelly, itm_mutagen, itm_purifier, NULL); + break; + + case CARAVAN_CLOTHES: + setvector(ret, +itm_backpack, itm_vest, itm_trenchcoat, itm_jacket_leather, itm_kevlar, +itm_gloves_fingerless, itm_mask_filter, itm_mask_gas, itm_glasses_eye, +itm_glasses_safety, itm_goggles_ski, itm_goggles_nv, itm_helmet_ball, +itm_helmet_riot, NULL); + break; + + case CARAVAN_TOOLS: + setvector(ret, +itm_screwdriver, itm_wrench, itm_saw, itm_hacksaw, itm_lighter, itm_sewing_kit, +itm_scissors, itm_extinguisher, itm_flashlight, itm_hotplate, +itm_soldering_iron, itm_shovel, itm_jackhammer, itm_landmine, itm_teleporter, +itm_grenade, itm_flashbang, itm_EMPbomb, itm_smokebomb, itm_bot_manhack, +itm_bot_turret, itm_UPS_off, itm_mininuke, NULL); + break; + } + + return ret; +} + +void draw_caravan_borders(WINDOW *w, int current_window) +{ +// First, do the borders for the category window + nc_color col = c_ltgray; + if (current_window == 0) + col = c_yellow; + + mvwputch(w, 0, 0, col, LINE_OXXO); + for (int i = 1; i <= 38; i++) { + mvwputch(w, 0, i, col, LINE_OXOX); + mvwputch(w, 11, i, col, LINE_OXOX); + } + for (int i = 1; i <= 10; i++) { + mvwputch(w, i, 0, col, LINE_XOXO); + mvwputch(w, i, 39, c_yellow, LINE_XOXO); // Shared border, always yellow + } + mvwputch(w, 11, 0, col, LINE_XXXO); + +// These are shared with the items window, and so are always "on" + mvwputch(w, 0, 39, c_yellow, LINE_OXXX); + mvwputch(w, 11, 39, c_yellow, LINE_XOXX); + + col = (current_window == 1 ? c_yellow : c_ltgray); +// Next, draw the borders for the item description window--always "off" & gray + for (int i = 12; i <= 23; i++) { + mvwputch(w, i, 0, c_ltgray, LINE_XOXO); + mvwputch(w, i, 39, col, LINE_XOXO); + } + for (int i = 1; i <= 38; i++) + mvwputch(w, 24, i, c_ltgray, LINE_OXOX); + + mvwputch(w, 24, 0, c_ltgray, LINE_XXOO); + mvwputch(w, 24, 39, c_ltgray, LINE_XXOX); + +// Finally, draw the item section borders + for (int i = 40; i <= 78; i++) { + mvwputch(w, 0, i, col, LINE_OXOX); + mvwputch(w, 24, i, col, LINE_OXOX); + } + for (int i = 1; i <= 23; i++) + mvwputch(w, i, 79, col, LINE_XOXO); + + mvwputch(w, 24, 39, col, LINE_XXOX); + mvwputch(w, 0, 79, col, LINE_OOXX); + mvwputch(w, 24, 79, col, LINE_XOOX); + +// Quick reminded about help. + mvwprintz(w, 24, 2, c_red, "Press ? for help."); + wrefresh(w); +} + +void draw_caravan_categories(WINDOW *w, int category_selected, int total_price, + int cash) +{ +// Clear the window + for (int i = 1; i <= 10; i++) + mvwprintz(w, i, 1, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + mvwprintz(w, 1, 1, c_white, "Your Cash:%s%d", + (cash >= 10000 ? " " : (cash >= 1000 ? " " : (cash >= 100 ? " " : + (cash >= 10 ? " " : " ")))), cash); + wprintz(w, c_ltgray, " -> "); + wprintz(w, (total_price > cash ? c_red : c_green), "%d", cash - total_price); + + for (int i = 0; i < NUM_CARAVAN_CATEGORIES; i++) + mvwprintz(w, i + 3, 1, (i == category_selected ? h_white : c_white), + caravan_category_name( caravan_category(i) ).c_str()); + wrefresh(w); +} + +void draw_caravan_items(WINDOW *w, game *g, std::vector *items, + std::vector *counts, int offset, + int item_selected) +{ +// Print the item info first. This is important, because it contains \n which +// will corrupt the item list. + +// Actually, clear the item info first. + for (int i = 12; i <= 23; i++) + mvwprintz(w, i, 1, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +// THEN print it--if item_selected is valid + if (item_selected < items->size()) { + item tmp(g->itypes[ (*items)[item_selected] ], 0); // Dummy item to get info + mvwprintz(w, 12, 0, c_white, tmp.info().c_str()); + } +// Next, clear the item list on the right + for (int i = 1; i <= 23; i++) + mvwprintz(w, i, 40, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +// Finally, print the item list on the right + for (int i = offset; i <= offset + 23 && i < items->size(); i++) { + mvwprintz(w, i - offset + 1, 40, (item_selected == i ? h_white : c_white), + g->itypes[ (*items)[i] ]->name.c_str()); + wprintz(w, c_white, " x %s%d", ((*counts)[i] >= 10 ? "" : " "), (*counts)[i]); + if ((*counts)[i] > 0) { + int price = caravan_price(g->u, g->itypes[(*items)[i]]->price *(*counts)[i]); + wprintz(w, (price > g->u.cash ? c_red : c_green), + "($%s%d)", (price >= 100000 ? "" : (price >= 10000 ? " " : + (price >= 1000 ? " " : (price >= 100 ? " " : + (price >= 10 ? " " : " "))))), price); + } + } + wrefresh(w); +} + +int caravan_price(player &u, int price) +{ + if (u.sklevel[sk_barter] > 10) + return int( double(price) * .5); + return int( double(price) * (1.0 - double(u.sklevel[sk_barter]) * .05)); +} + +void defense_game::spawn_wave(game *g) +{ + g->add_msg("********"); + int diff = initial_difficulty + current_wave * wave_difficulty; + bool themed_wave = one_in(SPECIAL_WAVE_CHANCE); // All a single monster type + g->u.cash += cash_per_wave + (current_wave - 1) * cash_increase; + std::vector valid; + valid = pick_monster_wave(g); + while (diff > 0) { +// Clear out any monsters that exceed our remaining difficulty + for (int i = 0; i < valid.size(); i++) { + if (g->mtypes[valid[i]]->difficulty > diff) { + valid.erase(valid.begin() + i); + i--; + } + } + if (valid.size() == 0) { + g->add_msg("Welcome to Wave %d!", current_wave); + g->add_msg("********"); + return; + } + int rn = rng(0, valid.size() - 1); + mtype *type = g->mtypes[valid[rn]]; + if (themed_wave) { + int num = diff / type->difficulty; + if (num >= SPECIAL_WAVE_MIN) { +// TODO: Do we want a special message here? + for (int i = 0; i < num; i++) + spawn_wave_monster(g, type); + g->add_msg( special_wave_message(type->name).c_str() ); + g->add_msg("********"); + return; + } else + themed_wave = false; // No partially-themed waves + } + diff -= type->difficulty; + spawn_wave_monster(g, type); + } + g->add_msg("Welcome to Wave %d!", current_wave); + g->add_msg("********"); +} + +std::vector defense_game::pick_monster_wave(game *g) +{ + std::vector valid; + std::vector ret; + + if (zombies || specials) { + if (specials) + valid.push_back(mcat_zombie); + else + valid.push_back(mcat_vanilla_zombie); + } + if (spiders) + valid.push_back(mcat_spider); + if (triffids) + valid.push_back(mcat_triffid); + if (robots) + valid.push_back(mcat_robot); + if (subspace) + valid.push_back(mcat_nether); + + if (valid.empty()) + debugmsg("Couldn't find a valid monster group for defense!"); + else + ret = g->moncats[ valid[rng(0, valid.size() - 1)] ]; + + return ret; +} + +void defense_game::spawn_wave_monster(game *g, mtype *type) +{ + monster tmp(type); + if (location == DEFLOC_HOSPITAL || location == DEFLOC_MALL) { + tmp.posy = SEEY; // Always spawn to the north! + tmp.posx = rng(SEEX * (MAPSIZE / 2), SEEX * (1 + MAPSIZE / 2)); + } else if (one_in(2)) { + tmp.spawn(rng(SEEX * (MAPSIZE / 2), SEEX * (1 + MAPSIZE / 2)), rng(1, SEEY)); + if (one_in(2)) + tmp.posy = SEEY * MAPSIZE - 1 - tmp.posy; + } else { + tmp.spawn(rng(1, SEEX), rng(SEEY * (MAPSIZE / 2), SEEY * (1 + MAPSIZE / 2))); + if (one_in(2)) + tmp.posx = SEEX * MAPSIZE - 1 - tmp.posx; + } + tmp.wandx = g->u.posx; + tmp.wandy = g->u.posy; + tmp.wandf = 150; + g->z.push_back(tmp); +} + +std::string defense_game::special_wave_message(std::string name) +{ + std::stringstream ret; + ret << "Wave " << current_wave << ": "; + name[0] += 'A' - 'a'; // Capitalize + + for (int i = 2; i < name.size(); i++) { + if (name[i - 1] == ' ') + name[i] += 'A' - 'a'; + } + + switch (rng(1, 6)) { + case 1: ret << name << " Invasion!"; break; + case 2: ret << "Attack of the " << name << "s!"; break; + case 3: ret << name << " Attack!"; break; + case 4: ret << name << "s from Hell!"; break; + case 5: ret << "Beware! " << name << "!"; break; + case 6: ret << "The Day of the " << name << "!"; break; + case 7: ret << name << " Party!"; break; + case 8: ret << "Revenge of the " << name << "s!"; break; + case 9: ret << "Rise of the " << name << "s!"; break; + } + + return ret.str(); +} diff --git a/field.cpp b/field.cpp index e060a0d2fa..2a9e9d66f1 100644 --- a/field.cpp +++ b/field.cpp @@ -186,6 +186,12 @@ bool map::process_fields_in_submap(game *g, int gridn) if (cur->density == 3) ter(x, y) = t_rubble; + } else if (has_flag(l_flammable, x, y) && one_in(62 - cur->density * 10)) { + cur->age -= cur->density * cur->density * 30; + smoke += 10; + if (cur->density == 3) + ter(x, y) = t_rubble; + } else if (terlist[ter(x, y)].flags & mfb(swimmable)) cur->age += 800; // Flames die quickly on water @@ -229,6 +235,8 @@ bool map::process_fields_in_submap(game *g, int gridn) (!in_pit || ter(fx, fy) == t_pit) && ((cur->density == 3 && (has_flag(flammable, fx, fy) || one_in(20))) || + (cur->density == 3 && + (has_flag(l_flammable, fx, fy) && one_in(10))) || flammable_items_at(fx, fy) || field_at(fx, fy).type == fd_web)) { if (field_at(fx, fy).type == fd_smoke || diff --git a/game.cpp b/game.cpp index 2b7db3e364..fcb721bc23 100644 --- a/game.cpp +++ b/game.cpp @@ -35,11 +35,13 @@ game::game() init_missions(); // Set up mission templates (SEE missiondef.cpp) init_construction(); // Set up constructables (SEE construction.cpp) init_mutations(); + load_keyboard_settings(); + + gamemode = new special_game; // Nothing, basically. m = map(&itypes, &mapitems, &traps); // Init the root map with our vectors // Set up the main UI windows. -// Aw hell, we getting ncursey up in here! w_terrain = newwin(SEEY * 2 + 1, SEEX * 2 + 1, 0, 0); werase(w_terrain); w_minimap = newwin(7, 7, 0, SEEX * 2 + 1); @@ -65,7 +67,6 @@ game::game() curmes = 0; // We haven't read any messages yet uquit = QUIT_NO; // We haven't quit the game debugmon = false; // We're not printing debug messages - in_tutorial = false; // We're not in a tutorial game weather = WEATHER_CLEAR; // Start with some nice weather... nextweather = MINUTES(STARTING_MINUTES + 30); // Weather shift in 30 turnssincelastmon = 0; //Auto safe mode init @@ -89,6 +90,7 @@ game::game() game::~game() { + delete gamemode; for (int i = 0; i < itypes.size(); i++) delete itypes[i]; for (int i = 0; i < mtypes.size(); i++) @@ -167,7 +169,7 @@ fivedozenwhales@gmail.com."); mvwprintz(w_open, 4, 1, (sel1 == 0 ? h_white : c_white), "MOTD"); mvwprintz(w_open, 5, 1, (sel1 == 1 ? h_white : c_white), "New Game"); mvwprintz(w_open, 6, 1, (sel1 == 2 ? h_white : c_white), "Load Game"); - mvwprintz(w_open, 7, 1, (sel1 == 3 ? h_white : c_white), "Tutorial"); + mvwprintz(w_open, 7, 1, (sel1 == 3 ? h_white : c_white), "Special..."); mvwprintz(w_open, 8, 1, (sel1 == 4 ? h_white : c_white), "Help"); mvwprintz(w_open, 9, 1, (sel1 == 5 ? h_white : c_white), "Quit"); @@ -208,10 +210,6 @@ fivedozenwhales@gmail.com."); refresh(); wrefresh(w_open); refresh(); - } else if (sel1 == 3) { - u.normalize(this); - start_tutorial(TUT_BASIC); - return true; } else { sel2 = 1; layer = 2; @@ -219,9 +217,9 @@ fivedozenwhales@gmail.com."); mvwprintz(w_open, 4, 1, (sel1 == 0 ? c_white : c_dkgray), "MOTD"); mvwprintz(w_open, 5, 1, (sel1 == 1 ? c_white : c_dkgray), "New Game"); mvwprintz(w_open, 6, 1, (sel1 == 2 ? c_white : c_dkgray), "Load Game"); - mvwprintz(w_open, 7, 1, (sel1 == 3 ? c_white : c_dkgray), "Tutorial"); - mvwprintz(w_open, 8, 1, (sel1 == 3 ? c_white : c_dkgray), "Help"); - mvwprintz(w_open, 9, 1, (sel1 == 4 ? c_white : c_dkgray), "Quit"); + mvwprintz(w_open, 7, 1, (sel1 == 3 ? c_white : c_dkgray), "Special..."); + mvwprintz(w_open, 8, 1, (sel1 == 4 ? c_white : c_dkgray), "Help"); + mvwprintz(w_open, 9, 1, (sel1 == 5 ? c_white : c_dkgray), "Quit"); } } else if (layer == 2) { if (sel1 == 1) { // New Character @@ -319,6 +317,38 @@ fivedozenwhales@gmail.com."); ch = 0; } } + } else if (sel1 == 3) { // Special game + for (int i = 1; i < NUM_SPECIAL_GAMES; i++) { + mvwprintz(w_open, 6 + i, 12, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + mvwprintz(w_open, 6 + i, 12, (sel2 == i ? h_white : c_white), + special_game_name( special_game_id(i) ).c_str()); + } + wrefresh(w_open); + refresh(); + ch = input(); + if (ch == 'k') { + if (sel2 > 1) + sel2--; + else + sel2 = NUM_SPECIAL_GAMES - 1; + } else if (ch == 'j') { + if (sel2 < NUM_SPECIAL_GAMES - 1) + sel2++; + else + sel2 = 1; + } else if (ch == 'h' || ch == '<' || ch == KEY_ESCAPE) { + layer = 1; + for (int i = 6; i < 15; i++) + mvwprintz(w_open, i, 12, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + } + if (ch == 'l' || ch == '\n' || ch == '>') { + if (sel2 >= 1 && sel2 < NUM_SPECIAL_GAMES) { + gamemode = get_special_game( special_game_id(sel2) ); + gamemode->init(this); + start = true; + ch = 0; + } + } } } else if (layer == 3) { // Character Templates if (templates.size() == 0) @@ -421,60 +451,6 @@ void game::start_game() } -void game::start_tutorial(tut_type type) -{ - turn = HOURS(12); // Start at noon - for (int i = 0; i < NUM_LESSONS; i++) - tutorials_seen[i] = false; -// Set the scent map to 0 - for (int i = 0; i < SEEX * MAPSIZE; i++) { - for (int j = 0; j < SEEX * MAPSIZE; j++) - grscent[i][j] = 0; - } - temperature = 65; - in_tutorial = true; - switch (type) { - case TUT_NULL: - debugmsg("Null tutorial requested."); - return; - case TUT_BASIC: -// We use a Z-factor of 10 so that we don't plop down tutorial rooms in the -// middle of the "real" game world - u.name = "John Smith"; - levx = 100; - levy = 99; - cur_om = overmap(this, 0, 0, TUTORIAL_Z - 1); - cur_om.make_tutorial(); - cur_om.save(u.name, 0, 0, 9); - cur_om = overmap(this, 0, 0, TUTORIAL_Z); - cur_om.make_tutorial(); - u.toggle_trait(PF_QUICK); - u.inv.push_back(item(itypes[itm_lighter], 0, 'e')); - u.sklevel[sk_gun] = 5; - u.sklevel[sk_melee] = 5; -// Start the overmap out with all of it seen by the player - for (int i = 0; i < OMAPX; i++) { - for (int j = 0; j < OMAPX; j++) - cur_om.seen(i, j) = true; - } -// Init the starting map at this location. - for (int i = 0; i <= MAPSIZE; i += 2) { - for (int j = 0; j <= MAPSIZE; j += 2) { - tinymap tm(&itypes, &mapitems, &traps); - tm.generate(this, &cur_om, levx + i - 1, levy + j - 1, int(turn)); - } - } - m.load(this, levx, levy); - levz = 0; - u.posx = SEEX + 2; - u.posy = SEEY + 4; - break; - default: - debugmsg("Haven't made that tutorial yet."); - return; - } -} - void game::create_factions() { int num = dice(4, 3); @@ -504,7 +480,7 @@ void game::create_starting_npcs() tmp.mission = NPC_MISSION_SHELTER; tmp.chatbin.first_topic = TALK_SHELTER; if (one_in(2)) - tmp.chatbin.missions.push_back( reserve_mission(MISSION_GET_SOFTWARE, tmp.id)); + tmp.chatbin.missions.push_back(reserve_mission(MISSION_GET_SOFTWARE, tmp.id)); else tmp.chatbin.missions.push_back( reserve_random_mission(ORIGIN_OPENER_NPC, om_location(), tmp.id) ); @@ -541,23 +517,13 @@ bool game::do_turn() death_screen(); return true; } + gamemode->per_turn(this); turn.increment(); process_events(); process_missions(); if (turn.hour == 0 && turn.minute == 0 && turn.second == 0) // Midnight! cur_om.process_mongroups(); - if (in_tutorial) { - if (turn == HOURS(12) + 1) { - tutorial_message(LESSON_INTRO); // Goes through a list of intro topics - tutorial_message(LESSON_INTRO); - } else if (turn == HOURS(12) + 3) - tutorial_message(LESSON_INTRO); - if (turn == 50) { - monster tmp(mtypes[mon_zombie], 3, 3); - z.push_back(tmp); - } - } // Check if we've overdosed... in any deadly way. if (u.stim > 250) { add_msg("You have a sudden heart attack!"); @@ -742,15 +708,6 @@ void game::process_activity() } else { add_msg("You reload your %s.", u.weapon.tname(this).c_str()); u.recoil = 6; - if (in_tutorial) { - tutorial_message(LESSON_GUN_FIRE); - monster tmp(mtypes[mon_zombie], u.posx, u.posy - 6); - z.push_back(tmp); - tmp.spawn(u.posx + 2, u.posy - 5); - z.push_back(tmp); - tmp.spawn(u.posx - 2, u.posy - 5); - z.push_back(tmp); - } } break; @@ -808,27 +765,34 @@ void game::cancel_activity() u.activity.type = ACT_NULL; } -void game::cancel_activity_query(std::string message) +void game::cancel_activity_query(const char* message, ...) { + char buff[1024]; + va_list ap; + va_start(ap, message); + vsprintf(buff, message, ap); + va_end(ap); + std::string s(buff); + switch (u.activity.type) { case ACT_READ: - if (query_yn("%s Stop reading?", message.c_str())) + if (query_yn("%s Stop reading?", s.c_str())) u.activity.type = ACT_NULL; break; case ACT_RELOAD: - if (query_yn("%s Stop reloading?", message.c_str())) + if (query_yn("%s Stop reloading?", s.c_str())) u.activity.type = ACT_NULL; break; case ACT_CRAFT: - if (query_yn("%s Stop crafting?", message.c_str())) + if (query_yn("%s Stop crafting?", s.c_str())) u.activity.type = ACT_NULL; break; case ACT_BUTCHER: - if (query_yn("%s Stop butchering?", message.c_str())) + if (query_yn("%s Stop butchering?", s.c_str())) u.activity.type = ACT_NULL; break; case ACT_BUILD: - if (query_yn("%s Stop construction?", message.c_str())) + if (query_yn("%s Stop construction?", s.c_str())) u.activity.type = ACT_NULL; break; default: @@ -872,7 +836,7 @@ void game::update_weather() std::stringstream weather_text; weather_text << "The weather changed to " << weather_data[weather].name << "!"; - cancel_activity_query(weather_text.str()); + cancel_activity_query(weather_text.str().c_str()); } // Now update temperature @@ -1099,154 +1063,277 @@ void game::process_missions() void game::get_input() { - char ch = input(); // See keypress.h - - last_action = ch; - -// These are the default characters for all actions. It's the job of input(), -// found in keypress.h, to translate the user's input into these characters. - if (ch == 'y' || ch == 'u' || ch == 'h' || ch == 'j' || ch == 'k' || - ch == 'l' || ch == 'b' || ch == 'n') { - int movex, movey; - get_direction(movex, movey, ch); - plmove(movex, movey); - } else if (ch == '>') - vertical_move(-1, false); - else if (ch == '<') - vertical_move( 1, false); - else if (ch == '.') { - if (run_mode == 2) // Monsters around and we don't wanna pause - add_msg("Monster spotted--safe mode is on! (Press '!' to turn it off.)"); - else - u.pause(); - } else if (ch == 'o') - open(); - else if (ch == 'c') - close(); - else if (ch == 'p') { - u.power_bionics(this); - refresh_all(); - } else if (ch == 'e') - examine(); - else if (ch == ';' || ch == 'x') - look_around(); - else if (ch == ',' || ch == 'g') - pickup(u.posx, u.posy, 1); - else if (ch == 'd') - drop(); - else if (ch == 'D') - drop_in_direction(); - else if (ch == '=') - reassign_item(); - else if (ch == 'i') { - bool has = false; - do { - char ch = inv(); - has = u.has_item(ch); - if (has) - full_screen_popup(u.i_at(ch).info(true).c_str()); - } while (has); - refresh_all(); - } else if (ch == 'B') - butcher(); - else if (ch == 'E') - eat(); - else if (ch == 'a') - use_item(); - else if (ch == 'W') - wear(); - else if (ch == 'w') - wield(); - else if (ch == '^') - wait(); - else if (ch == 'T') - takeoff(); - else if (ch == 'r') - reload(); - else if (ch == 'U') - unload(); - else if (ch == 'R') - read(); - else if (ch == 't') - plthrow(); - else if (ch == 'f') - plfire(false); - else if (ch == 'F') - plfire(true); - else if (ch == 'C') - chat(); - else if (ch == 'Z') - debug(); - else if (ch == '-') - display_scent(); - else if (ch == '~') { - debugmon = !debugmon; - add_msg("Debug messages %s!", (debugmon ? "ON" : "OFF")); - } else if (ch == ':' || ch == 'm') - draw_overmap(); - else if (ch == 'M') - list_missions(); - else if (ch == '@') { - u.disp_info(this); - refresh_all(); - } else if (ch == '#') - list_factions(); - else if (ch == '%' || ch == '+') { - u.disp_morale(); - refresh_all(); - } else if (ch == '&' || ch == '/') - craft(); - else if (ch == '*') - construction_menu(); - else if (ch == '$' && query_yn("Are you sure you want to sleep?")) { - u.try_to_sleep(this); - u.moves = 0; - } else if (ch == '!') { - if (run_mode == 0 ) { - run_mode = 1; - add_msg("Safe mode ON!"); - } else { - turnssincelastmon = 0; - run_mode = 0; - if(autosafemode) - add_msg("Safe mode OFF! (Auto safe mode still enabled!)"); + char ch = input(); // See keypress.h - translates keypad and arrows to vikeys + + if (keymap.find(ch) == keymap.end()) { + add_msg("Unknown command: '%c'", ch); + return; + } + + action_id act = keymap[ch]; + + gamemode->pre_action(this, act); + + switch (act) { + + case ACTION_PAUSE: + if (run_mode == 2) // Monsters around and we don't wanna pause + add_msg("Monster spotted--safe mode is on! (Press '!' to turn it off.)"); else - add_msg("Safe mode OFF!"); - } - } else if (ch == 's') - smash(); - else if (ch == 'S' && query_yn("Save and quit?")) { - save(); - u.moves = 0; - uquit = QUIT_SAVED; - } else if (ch == 'Q' && query_yn("Commit suicide?")) { - u.moves = 0; - std::vector tmp = u.inv_dump(); - item your_body; - your_body.make_corpse(itypes[itm_corpse], mtypes[mon_null], turn); - your_body.name = u.name; - m.add_item(u.posx, u.posy, your_body); - for (int i = 0; i < tmp.size(); i++) - m.add_item(u.posx, u.posy, tmp[i]); - m.save(&cur_om, turn, levx, levy); - uquit = QUIT_SUICIDE; - } else if (ch == '?') { - help(); - refresh_all(); - } else if (ch == '"') { - if (autosafemode) { - add_msg("Auto safe mode OFF!"); - autosafemode = false; - } else { - add_msg("Auto safe mode ON"); - autosafemode = true; - } - } else if (ch == '\''){ - if (run_mode == 2) { - add_msg("Ignoring enemy!"); - run_mode = 1; - } + u.pause(); + break; + + case ACTION_MOVE_N: + plmove(0, -1); + break; + + case ACTION_MOVE_NE: + plmove(1, -1); + break; + + case ACTION_MOVE_E: + plmove(1, 0); + break; + + case ACTION_MOVE_SE: + plmove(1, 1); + break; + + case ACTION_MOVE_S: + plmove(0, 1); + break; + + case ACTION_MOVE_SW: + plmove(-1, 1); + break; + + case ACTION_MOVE_W: + plmove(-1, 0); + break; + + case ACTION_MOVE_NW: + plmove(-1, -1); + break; + + case ACTION_MOVE_DOWN: + vertical_move(-1, false); + break; + + case ACTION_MOVE_UP: + vertical_move( 1, false); + break; + + case ACTION_OPEN: + open(); + break; + + case ACTION_CLOSE: + close(); + break; + + case ACTION_SMASH: + smash(); + break; + + case ACTION_EXAMINE: + examine(); + break; + + case ACTION_PICKUP: + pickup(u.posx, u.posy, 1); + break; + + case ACTION_BUTCHER: + butcher(); + break; + + case ACTION_CHAT: + chat(); + break; + + case ACTION_LOOK: + look_around(); + break; + + case ACTION_INVENTORY: { + bool has = false; + do { + char ch = inv(); + has = u.has_item(ch); + if (has) + full_screen_popup(u.i_at(ch).info(true).c_str()); + } while (has); + refresh_all(); + } break; + + case ACTION_USE: + use_item(); + break; + + case ACTION_WEAR: + wear(); + break; + + case ACTION_TAKE_OFF: + takeoff(); + break; + + case ACTION_EAT: + eat(); + break; + + case ACTION_READ: + read(); + break; + + case ACTION_WIELD: + wield(); + break; + + case ACTION_RELOAD: + reload(); + break; + + case ACTION_UNLOAD: + unload(); + break; + + case ACTION_THROW: + plthrow(); + break; + + case ACTION_FIRE: + plfire(false); + break; + + case ACTION_FIRE_BURST: + plfire(true); + break; + + case ACTION_DROP: + drop(); + break; + + case ACTION_DIR_DROP: + drop_in_direction(); + break; + + case ACTION_BIONICS: + u.power_bionics(this); + refresh_all(); + break; + + case ACTION_WAIT: + wait(); + break; + + case ACTION_CRAFT: + craft(); + break; + + case ACTION_CONSTRUCT: + construction_menu(); + break; + + case ACTION_SLEEP: + if (query_yn("Are you sure you want to sleep?")) { + u.try_to_sleep(this); + u.moves = 0; + } + break; + + case ACTION_TOGGLE_SAFEMODE: + if (run_mode == 0 ) { + run_mode = 1; + add_msg("Safe mode ON!"); + } else { + turnssincelastmon = 0; + run_mode = 0; + if(autosafemode) + add_msg("Safe mode OFF! (Auto safe mode still enabled!)"); + else + add_msg("Safe mode OFF!"); + } + break; + + case ACTION_TOGGLE_AUTOSAFE: + if (autosafemode) { + add_msg("Auto safe mode OFF!"); + autosafemode = false; + } else { + add_msg("Auto safe mode ON"); + autosafemode = true; + } + break; + + case ACTION_IGNORE_ENEMY: + if (run_mode == 2) { + add_msg("Ignoring enemy!"); + run_mode = 1; + } + break; + + case ACTION_SAVE: + if (query_yn("Save and quit?")) { + save(); + u.moves = 0; + uquit = QUIT_SAVED; + } + break; + + case ACTION_QUIT: + if (query_yn("Commit suicide?")) { + u.moves = 0; + std::vector tmp = u.inv_dump(); + item your_body; + your_body.make_corpse(itypes[itm_corpse], mtypes[mon_null], turn); + your_body.name = u.name; + m.add_item(u.posx, u.posy, your_body); + for (int i = 0; i < tmp.size(); i++) + m.add_item(u.posx, u.posy, tmp[i]); + m.save(&cur_om, turn, levx, levy); + uquit = QUIT_SUICIDE; + } + break; + + case ACTION_PL_INFO: + u.disp_info(this); + refresh_all(); + break; + + case ACTION_MAP: + draw_overmap(); + break; + + case ACTION_FACTIONS: + list_factions(); + break; + + case ACTION_MORALE: + u.disp_morale(); + refresh_all(); + break; + + case ACTION_HELP: + help(); + refresh_all(); + break; + + case ACTION_DEBUG: + debug(); + break; + + case ACTION_DISPLAY_SCENT: + display_scent(); + break; + + case ACTION_TOGGLE_DEBUGMON: + debugmon = !debugmon; + add_msg("Debug messages %s!", (debugmon ? "ON" : "OFF")); + break; } + + gamemode->post_action(this, act); } int& game::scent(int x, int y) @@ -1326,6 +1413,7 @@ bool game::is_game_over() void game::death_screen() { + gamemode->game_over(this); std::stringstream playerfile; playerfile << "save/" << u.name << ".sav"; unlink(playerfile.str().c_str()); @@ -1953,27 +2041,6 @@ void game::list_missions() refresh_all(); } -void game::tutorial_message(tut_lesson lesson) -{ -// Cycle through intro lessons - if (lesson == LESSON_INTRO) { - while (lesson != NUM_LESSONS && tutorials_seen[lesson]) { - switch (lesson) { - case LESSON_INTRO: lesson = LESSON_MOVE; break; - case LESSON_MOVE: lesson = LESSON_LOOK; break; - case LESSON_LOOK: lesson = NUM_LESSONS; break; - } - } - if (lesson == NUM_LESSONS) - return; - } - if (tutorials_seen[lesson]) - return; - tutorials_seen[lesson] = true; - popup_top(tut_text[lesson].c_str()); - refresh_all(); -} - void game::draw() { // Draw map @@ -2071,12 +2138,6 @@ void game::draw_ter() wrefresh(w_terrain); if (u.has_disease(DI_VISUALS)) hallucinate(); - if (in_tutorial && light_level() == 1) { - if (u.has_amount(itm_flashlight, 1)) - tutorial_message(LESSON_DARK); - else - tutorial_message(LESSON_DARK_NO_FLASH); - } } void game::refresh_all() @@ -2491,7 +2552,7 @@ void game::mon_info() int direction; for (int i = 0; i < z.size(); i++) { if (u_see(&(z[i]), buff)) { - if (!z[i].is_fleeing(u) && z[i].friendly == 0) + if (z[i].attitude(&u) == MATT_ATTACK || z[i].attitude(&u) == MATT_FOLLOW) newseen++; if (z[i].posx < u.posx - SEEX) { if (z[i].posy < u.posy - SEEY) @@ -2639,6 +2700,7 @@ void game::monmove() dead = true; } } + m.mon_in_field(z[i].posx, z[i].posy, this, &(z[i])); while (z[i].moves > 0 && !dead) { z[i].made_footstep = false; z[i].plan(this); // Formulate a path to follow @@ -2648,13 +2710,12 @@ void game::monmove() if (z[i].hurt(0)) { // Maybe we died... kill_mon(i); dead = true; + z[i].dead = true; } } if (dead) i--; else { - if (in_tutorial && u.pain > 0) - tutorial_message(LESSON_PAIN); if (u.has_active_bionic(bio_alarm) && u.power_level >= 1 && abs(z[i].posx - u.posx) <= 5 && abs(z[i].posy - u.posy) <= 5) { u.power_level--; @@ -2713,7 +2774,8 @@ void game::om_npcs_move() /* for (int i = 0; i < cur_om.npcs.size(); i++) { cur_om.npcs[i].perform_mission(this); - if (abs(cur_om.npcs[i].mapx - levx) <= 1 && abs(cur_om.npcs[i].mapy - levy) <= 1) { + if (abs(cur_om.npcs[i].mapx - levx) <= 1 && + abs(cur_om.npcs[i].mapy - levy) <= 1 ) { cur_om.npcs[i].posx = u.posx + SEEX * 2 * (cur_om.npcs[i].mapx - levx); cur_om.npcs[i].posy = u.posy + SEEY * 2 * (cur_om.npcs[i].mapy - levy); active_npc.push_back(cur_om.npcs[i]); @@ -2806,10 +2868,9 @@ void game::sound(int x, int y, int vol, std::string description) for (int i = 0; i < z.size(); i++) { if (z[i].can_hear()) { int dist = rl_dist(x, y, z[i].posx, z[i].posy); - if (z[i].has_flag(MF_GOODHEARING) && int(dist / 2) <= vol) - z[i].wander_to(x, y, vol - int(dist / 2)); - else if (dist <= vol && dist >= 2) // Adjacent sounds are this monster - z[i].wander_to(x, y, vol - dist); + int volume = vol - (z[i].has_flag(MF_GOODHEARING) ? int(dist / 2) : dist); + z[i].wander_to(x, y, volume); + z[i].process_trigger(MTRIG_SOUND, volume); } } // Loud sounds make the next spawn sooner! @@ -2849,7 +2910,8 @@ void game::sound(int x, int y, int vol, std::string description) add_msg("You're woken up by a noise."); return; } - cancel_activity_query("Heard a noise!"); + cancel_activity_query("Heard %s!", + (description == "" ? "a noise" : description.c_str())); // We need to figure out where it was coming from, relative to the player int dx = x - u.posx; int dy = y - u.posy; @@ -3256,15 +3318,6 @@ void game::kill_mon(int index) for (int i = 0; i < z[index].inv.size(); i++) m.add_item(z[index].posx, z[index].posy, z[index].inv[i]); z[index].die(this); -// If they left a corpse, give a tutorial message on butchering - if (in_tutorial && !(tutorials_seen[LESSON_BUTCHER])) { - for (int i = 0; i < m.i_at(z[index].posx, z[index].posy).size(); i++) { - if (m.i_at(z[index].posx, z[index].posy)[i].type->id == itm_corpse) { - tutorial_message(LESSON_BUTCHER); - i = m.i_at(z[index].posx, z[index].posy).size(); - } - } - } z.erase(z.begin()+index); if (last_target == index) last_target = -1; @@ -3383,8 +3436,6 @@ void game::open() u.moves += 100; } } - if (in_tutorial) - tutorial_message(LESSON_CLOSE); } void game::close() @@ -3412,8 +3463,6 @@ void game::close() add_msg("Invalid direction."); if (didit) u.moves -= 90; - if (in_tutorial) - tutorial_message(LESSON_SMASH); } void game::smash() @@ -3471,17 +3520,7 @@ void game::use_item() return; } last_action += ch; - itype_id tut; - if (in_tutorial) - tut = itype_id(u.i_at(ch).type->id); u.use(this, ch); - - if (in_tutorial) { - if (tut == itm_grenade) - tutorial_message(LESSON_ACT_GRENADE); - else if (tut == itm_bubblewrap) - tutorial_message(LESSON_ACT_BUBBLEWRAP); - } } void game::examine() @@ -3523,8 +3562,6 @@ void game::examine() %s is firmly sealed.", m.tername(examx, examy).c_str()); } } else { - if (in_tutorial && m.i_at(examx, examy).size() == 0) - tutorial_message(LESSON_INTERACT); if (m.i_at(examx, examy).size() == 0 && m.has_flag(container, examx, examy) && !(m.has_flag(swimmable, examx, examy) || m.ter(examx, examy) == t_toilet)) add_msg("It is empty."); @@ -3901,21 +3938,6 @@ void game::pickup(int posx, int posy, int min) u.wield(this, u.inv.size() - 1); u.moves -= 100; add_msg("Wielding %c - %s", newit.invlet, newit.tname(this).c_str()); - if (in_tutorial) { - tutorial_message(LESSON_FULL_INV); - if (newit.is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (newit.is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (newit.is_weap()) - tutorial_message(LESSON_GOT_WEAPON); - else if (newit.is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (newit.is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (newit.is_food() || newit.is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } else nextinv--; } else { @@ -3924,19 +3946,6 @@ void game::pickup(int posx, int posy, int min) m.i_clear(posx, posy); u.moves -= 100; add_msg("Wielding %c - %s", newit.invlet, newit.tname(this).c_str()); - if (in_tutorial) { - tutorial_message(LESSON_WIELD_NO_SPACE); - if (newit.is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (newit.is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (newit.is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (newit.is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (newit.is_food() || newit.is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } } else if (!u.is_armed() && (u.volume_carried() + newit.volume() > u.volume_capacity() - 2 || @@ -3945,49 +3954,16 @@ void game::pickup(int posx, int posy, int min) m.i_clear(posx, posy); u.moves -= 100; add_msg("Wielding %c - %s", newit.invlet, newit.tname(this).c_str()); - if (in_tutorial) { - if (newit.is_weap()) - tutorial_message(LESSON_AUTOWIELD); - else - tutorial_message(LESSON_WIELD_NO_SPACE); - if (newit.is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (newit.is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (newit.is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (newit.is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (newit.is_food() || newit.is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } else { u.i_add(newit); m.i_clear(posx, posy); u.moves -= 100; add_msg("%c - %s", newit.invlet, newit.tname(this).c_str()); - if (in_tutorial) { - tutorial_message(LESSON_ITEM_INTO_INV); - if (newit.is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (newit.is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (newit.is_weap()) - tutorial_message(LESSON_GOT_WEAPON); - else if (newit.is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (newit.is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (newit.is_food() || newit.is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } if (weight_is_okay && u.weight_carried() >= u.weight_capacity() * .25) add_msg("You're overburdened!"); if (volume_is_okay && u.volume_carried() > u.volume_capacity() - 2) { add_msg("You struggle to carry such a large volume!"); - if (in_tutorial) - tutorial_message(LESSON_OVERLOADED); } return; } @@ -4139,21 +4115,6 @@ void game::pickup(int posx, int posy, int min) u.moves -= 100; m.i_rem(posx, posy, curmit); curmit--; - if (in_tutorial) { - tutorial_message(LESSON_WIELD_NO_SPACE); - if (here[i].is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (here[i].is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (here[i].is_weap()) - tutorial_message(LESSON_GOT_WEAPON); - else if (here[i].is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (here[i].is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (here[i].is_food() || here[i].is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } else nextinv--; } else { @@ -4162,21 +4123,6 @@ void game::pickup(int posx, int posy, int min) m.i_rem(posx, posy, curmit); curmit--; u.moves -= 100; - if (in_tutorial) { - tutorial_message(LESSON_WIELD_NO_SPACE); - if (here[i].is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (here[i].is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (here[i].is_weap()) - tutorial_message(LESSON_GOT_WEAPON); - else if (here[i].is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (here[i].is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (here[i].is_food() || here[i].is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } } else if (!u.is_armed() && (u.volume_carried() + here[i].volume() > u.volume_capacity() - 2 || @@ -4185,44 +4131,11 @@ void game::pickup(int posx, int posy, int min) m.i_rem(posx, posy, curmit); u.moves -= 100; curmit--; - if (in_tutorial) { - if (here[i].is_weap()) - tutorial_message(LESSON_AUTOWIELD); - else - tutorial_message(LESSON_WIELD_NO_SPACE); - if (here[i].is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (here[i].is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (here[i].is_weap()) - tutorial_message(LESSON_GOT_WEAPON); - else if (here[i].is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (here[i].is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (here[i].is_food() || here[i].is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } else { u.i_add(here[i]); m.i_rem(posx, posy, curmit); u.moves -= 100; curmit--; - if (in_tutorial) { - tutorial_message(LESSON_ITEM_INTO_INV); - if (here[i].is_armor()) - tutorial_message(LESSON_GOT_ARMOR); - else if (here[i].is_gun()) - tutorial_message(LESSON_GOT_GUN); - else if (here[i].is_weap()) - tutorial_message(LESSON_GOT_WEAPON); - else if (here[i].is_ammo()) - tutorial_message(LESSON_GOT_AMMO); - else if (here[i].is_tool()) - tutorial_message(LESSON_GOT_TOOL); - else if (here[i].is_food() || here[i].is_food_container()) - tutorial_message(LESSON_GOT_FOOD); - } } } curmit++; @@ -4233,8 +4146,6 @@ void game::pickup(int posx, int posy, int min) add_msg("You're overburdened!"); if (volume_is_okay && u.volume_carried() > u.volume_capacity() - 2) { add_msg("You struggle to carry such a large volume!"); - if (in_tutorial) - tutorial_message(LESSON_OVERLOADED); } werase(w_pickup); wrefresh(w_pickup); @@ -4609,8 +4520,6 @@ void game::plfire(bool burst) u.practice(sk_gun, 5); fire(u, x, y, trajectory, burst); - if (in_tutorial && u.recoil >= 5) - tutorial_message(LESSON_RECOIL); } void game::butcher() @@ -4743,14 +4652,6 @@ void game::eat() add_msg("Never mind."); return; } - if (in_tutorial) { - if (u.i_at(ch).type->id == itm_codeine) - tutorial_message(LESSON_TOOK_PAINKILLER); - else if (u.i_at(ch).type->id == itm_cig) - tutorial_message(LESSON_TOOK_CIG); - else if (u.i_at(ch).type->id == itm_bottle_plastic) - tutorial_message(LESSON_DRANK_WATER); - } if (!u.has_item(ch)) { add_msg("You don't have item '%c'!", ch); return; @@ -4766,17 +4667,7 @@ void game::wear() add_msg("Never mind."); return; } - if (u.wear(this, ch)) { - if (in_tutorial) { - it_armor* armor = dynamic_cast(u.worn[u.worn.size() - 1].type); - if (armor->dmg_resist >= 2 || armor->cut_resist >= 4) - tutorial_message(LESSON_WORE_ARMOR); - else if (armor->storage >= 20) - tutorial_message(LESSON_WORE_STORAGE); - else if (armor->env_resist >= 2) - tutorial_message(LESSON_WORE_MASK); - } - } + u.wear(this, ch); } void game::takeoff() @@ -4940,9 +4831,6 @@ void game::wield() if (success) u.recoil = 5; - - if (in_tutorial && u.weapon.is_gun()) - tutorial_message(LESSON_GUN_LOAD); } void game::read() @@ -5194,38 +5082,9 @@ void game::plmove(int x, int y) } } -// Special tutorial messages - if (in_tutorial) { - bool showed_message = false; - for (int i = u.posx - 1; i <= u.posx + 1 && !showed_message; i++) { - for (int j = u.posy - 1; j <= u.posy + 1 && !showed_message; j++) { - if (m.ter(i, j) == t_door_c) { - showed_message = true; - tutorial_message(LESSON_OPEN); - } else if (m.ter(i, j) == t_door_o) { - showed_message = true; - tutorial_message(LESSON_CLOSE); - } else if (m.ter(i, j) == t_window) { - showed_message = true; - tutorial_message(LESSON_WINDOW); - } else if (m.ter(i, j) == t_rack && m.i_at(i, j).size() > 0) { - showed_message = true; - tutorial_message(LESSON_EXAMINE); - } else if (m.ter(i, j) == t_stairs_down) { - showed_message = true; - tutorial_message(LESSON_STAIRS); - } else if (m.ter(i, j) == t_water_sh) { - showed_message = true; - tutorial_message(LESSON_PICKUP_WATER); - } - } - } - } // List items here if (!u.has_disease(DI_BLIND) && m.i_at(x, y).size() <= 3 && m.i_at(x, y).size() != 0) { - if (in_tutorial) - tutorial_message(LESSON_PICKUP); std::string buff = "You see here "; for (int i = 0; i < m.i_at(x, y).size(); i++) { buff += m.i_at(x, y)[i].tname(this); @@ -5259,8 +5118,6 @@ void game::plmove(int x, int y) else if (m.ter(x, y) == t_door_locked || m.ter(x, y) == t_door_locked_alarm) { u.moves -= 100; add_msg("That door is locked!"); - if (in_tutorial) - tutorial_message(LESSON_SMASH); } } } @@ -5461,7 +5318,7 @@ void game::vertical_move(int movez, bool force) if (u.has_trait(PF_WINGS_BIRD)) add_msg("You flap your wings and flutter down gracefully."); else { - int dam = int((u.str_max / 4) + rng(5, 10)) * rng(1, 3);// The bigger they are + int dam = int((u.str_max / 4) + rng(5, 10)) * rng(1, 3);//The bigger they are dam -= rng(u.dodge(this), u.dodge(this) * 3); if (dam <= 0) add_msg("You fall expertly and take no damage."); @@ -5574,36 +5431,34 @@ void game::update_map(int &x, int &y) } } // Spawn static NPCs? - if (!in_tutorial) { - npc temp; - for (int i = 0; i < cur_om.npcs.size(); i++) { - if (rl_dist(levx + int(MAPSIZE / 2), levy + int(MAPSIZE / 2), - cur_om.npcs[i].mapx, cur_om.npcs[i].mapy) <= - int(MAPSIZE / 2) + 1) { - int dx = cur_om.npcs[i].mapx - levx, dy = cur_om.npcs[i].mapy - levy; + npc temp; + for (int i = 0; i < cur_om.npcs.size(); i++) { + if (rl_dist(levx + int(MAPSIZE / 2), levy + int(MAPSIZE / 2), + cur_om.npcs[i].mapx, cur_om.npcs[i].mapy) <= + int(MAPSIZE / 2) + 1) { + int dx = cur_om.npcs[i].mapx - levx, dy = cur_om.npcs[i].mapy - levy; + if (debugmon) + debugmsg("Spawning static NPC, %d:%d (%d:%d)", levx, levy, + cur_om.npcs[i].mapx, cur_om.npcs[i].mapy); + temp = cur_om.npcs[i]; + if (temp.posx == -1 || temp.posy == -1) { + debugmsg("Static NPC with no fine location data (%d:%d).", + temp.posx, temp.posy); + temp.posx = SEEX * 2 * (temp.mapx - levx) + rng(0 - SEEX, SEEX); + temp.posy = SEEY * 2 * (temp.mapy - levy) + rng(0 - SEEY, SEEY); + } else { if (debugmon) - debugmsg("Spawning static NPC, %d:%d (%d:%d)", levx, levy, - cur_om.npcs[i].mapx, cur_om.npcs[i].mapy); - temp = cur_om.npcs[i]; - if (temp.posx == -1 || temp.posy == -1) { - debugmsg("Static NPC with no fine location data (%d:%d).", - temp.posx, temp.posy); - temp.posx = SEEX * 2 * (temp.mapx - levx) + rng(0 - SEEX, SEEX); - temp.posy = SEEY * 2 * (temp.mapy - levy) + rng(0 - SEEY, SEEY); - } else { - if (debugmon) - debugmsg("Static NPC fine location %d:%d (%d:%d)", temp.posx, temp.posy, - temp.posx + dx * SEEX, temp.posy + dy * SEEY); - temp.posx += dx * SEEX; - temp.posy += dy * SEEY; - } - if (temp.marked_for_death) - temp.die(this, false); - else - active_npc.push_back(temp); - cur_om.npcs.erase(cur_om.npcs.begin() + i); - i--; + debugmsg("Static NPC fine location %d:%d (%d:%d)", temp.posx, temp.posy, + temp.posx + dx * SEEX, temp.posy + dy * SEEY); + temp.posx += dx * SEEX; + temp.posy += dy * SEEY; } + if (temp.marked_for_death) + temp.die(this, false); + else + active_npc.push_back(temp); + cur_om.npcs.erase(cur_om.npcs.begin() + i); + i--; } } // Spawn monsters if appropriate @@ -5961,6 +5816,7 @@ void game::wait() void game::gameover() { erase(); + gamemode->game_over(this); mvprintw(0, 35, "GAME OVER"); inv(); } diff --git a/game.h b/game.h index 822507fc63..6630a3ff6a 100644 --- a/game.h +++ b/game.h @@ -11,7 +11,6 @@ #include "crafting.h" #include "trap.h" #include "npc.h" -#include "tutorial.h" #include "faction.h" #include "event.h" #include "mission.h" @@ -21,7 +20,10 @@ #include "posix_time.h" #include "artifact.h" #include "mutation.h" +#include "gamemode.h" +#include "action.h" #include +#include #define LONG_RANGE 10 #define BLINK_SPEED 300 @@ -64,7 +66,6 @@ class game bool game_quit(); // True if we actually quit the game - used in main.cpp void save(); bool do_turn(); - void tutorial_message(tut_lesson lesson); void draw(); void draw_ter(); void advance_nextinv(); // Increment the next inventory letter @@ -104,7 +105,7 @@ class game void throw_item(player &p, int tarx, int tary, item &thrown, std::vector &trajectory); void cancel_activity(); - void cancel_activity_query(std::string message); + void cancel_activity_query(const char* message, ...); int assign_mission_id(); // Just returns the next available one void give_mission(mission_id type); void assign_mission(int id); @@ -159,10 +160,14 @@ class game std::vector itypes; std::vector mtypes; std::vector traps; + std::vector recipes; // The list of valid recipes + std::vector constructions; // The list of constructions + std::vector mapitems[num_itloc]; // Items at various map types std::vector monitems[num_monsters]; std::vector mission_types; // The list of mission templates mutation_branch mutation_data[PF_MAX2]; // Mutation data + std::map keymap; calendar turn; signed char temperature; // The air temperature @@ -198,7 +203,7 @@ class game bool load_master(); // Load the master data file, with factions &c void load(std::string name); // Load a player-specific save file void start_game(); // Starts a new game - void start_tutorial(tut_type type); // Starts a new tutorial + void start_special_game(special_game_id gametype); // See gamemode.cpp // Data Initialization void init_itypes(); // Initializes item types @@ -212,6 +217,8 @@ class game void init_missions(); // Initializes mission templates void init_mutations(); // Initializes mutation "tech tree" + void load_keyboard_settings(); // Load keybindings from disk + void create_factions(); // Creates new factions (for a new game world) void create_starting_npcs(); // Creates NPCs that start near you @@ -332,11 +339,7 @@ class game int kills[num_monsters]; // Player's kill count std::string last_action; // The keypresses of last turn - std::vector recipes; // The list of valid recipes - std::vector constructions; // The list of constructions - - bool tutorials_seen[NUM_LESSONS]; // Which tutorial lessons have we learned - bool in_tutorial; // True if we're in a tutorial right now + special_game *gamemode; }; #endif diff --git a/gamemode.cpp b/gamemode.cpp new file mode 100644 index 0000000000..96ed75918a --- /dev/null +++ b/gamemode.cpp @@ -0,0 +1,35 @@ +#include "gamemode.h" +#include "output.h" + +std::string special_game_name(special_game_id id) +{ + switch (id) { + case SGAME_NULL: + case NUM_SPECIAL_GAMES: return "nethack (this is a bug)"; + case SGAME_TUTORIAL: return "Tutorial"; + case SGAME_DEFENSE: return "Defense"; + default: return "Uncoded (this is a bug)"; + } +} + +special_game* get_special_game(special_game_id id) +{ + special_game* ret; + switch (id) { + case SGAME_NULL: + ret = new special_game; + break; + case SGAME_TUTORIAL: + ret = new tutorial_game; + break; + case SGAME_DEFENSE: + ret = new defense_game; + break; + default: + debugmsg("Missing something in get_special_game()?"); + ret = new special_game; + break; + } + + return ret; +} diff --git a/gamemode.h b/gamemode.h new file mode 100644 index 0000000000..9aa3fc2a1f --- /dev/null +++ b/gamemode.h @@ -0,0 +1,182 @@ +#ifndef _GAMEMODE_H_ +#define _GAMEMODE_H_ + +#include +#include +#include "action.h" +#include "itype.h" +#include "mtype.h" + +class game; +struct special_game; + +enum special_game_id { +SGAME_NULL = 0, +SGAME_TUTORIAL, +SGAME_DEFENSE, +NUM_SPECIAL_GAMES +}; + +std::string special_game_name(special_game_id id); +special_game* get_special_game(special_game_id id); + +struct special_game +{ + virtual special_game_id id() { return SGAME_NULL; }; +// init is run when the game begins + virtual void init(game *g) { }; +// per_turn is run every turn--before any player actions + virtual void per_turn(game *g) { }; +// pre_action is run after a keypress, but before the game handles the action +// It may modify the action, e.g. to cancel it + virtual void pre_action(game *g, action_id &act) { }; +// post_action is run after the game handles the action + virtual void post_action(game *g, action_id act) { }; +// game_over is run when the player dies (or the game otherwise ends) + virtual void game_over(game *g) { }; + +}; + +// TUTORIAL: + +enum tut_lesson { +LESSON_INTRO, +LESSON_MOVE, LESSON_LOOK, LESSON_OPEN, LESSON_CLOSE, LESSON_SMASH, + LESSON_WINDOW, LESSON_PICKUP, LESSON_EXAMINE, LESSON_INTERACT, + +LESSON_FULL_INV, LESSON_WIELD_NO_SPACE, LESSON_AUTOWIELD, LESSON_ITEM_INTO_INV, + LESSON_GOT_ARMOR, LESSON_GOT_WEAPON, LESSON_GOT_FOOD, LESSON_GOT_TOOL, + LESSON_GOT_GUN, LESSON_GOT_AMMO, LESSON_WORE_ARMOR, LESSON_WORE_STORAGE, + LESSON_WORE_MASK, + +LESSON_WEAPON_INFO, LESSON_HIT_MONSTER, LESSON_PAIN, LESSON_BUTCHER, + +LESSON_TOOK_PAINKILLER, LESSON_TOOK_CIG, LESSON_DRANK_WATER, + +LESSON_ACT_GRENADE, LESSON_ACT_BUBBLEWRAP, + +LESSON_OVERLOADED, + +LESSON_GUN_LOAD, LESSON_GUN_FIRE, LESSON_RECOIL, + +LESSON_STAIRS, LESSON_DARK_NO_FLASH, LESSON_DARK, LESSON_PICKUP_WATER, + +NUM_LESSONS +}; + +struct tutorial_game : public special_game +{ + virtual special_game_id id() { return SGAME_TUTORIAL; }; + virtual void init(game *g); + virtual void per_turn(game *g); + virtual void pre_action(game *g, action_id &act); + virtual void post_action(game *g, action_id act); + virtual void game_over(game *g) { }; + +private: + void add_message(game *g, tut_lesson lesson); + + bool tutorials_seen[NUM_LESSONS]; +}; + + +// DEFENSE + +enum defense_style { +DEFENSE_CUSTOM = 0, +DEFENSE_EASY, +DEFENSE_MEDIUM, +DEFENSE_HARD, +DEFENSE_SHAUN, +DEFENSE_DAWN, +DEFENSE_SPIDERS, +DEFENSE_TRIFFIDS, +DEFENSE_SKYNET, +DEFENSE_LOVECRAFT, +NUM_DEFENSE_STYLES +}; + +enum defense_location { +DEFLOC_NULL = 0, +DEFLOC_HOSPITAL, +DEFLOC_MALL, +DEFLOC_BAR, +DEFLOC_MANSION, +NUM_DEFENSE_LOCATIONS +}; + +enum caravan_category { +CARAVAN_CART = 0, +CARAVAN_MELEE, +CARAVAN_GUNS, +CARAVAN_COMPONENTS, +CARAVAN_FOOD, +CARAVAN_CLOTHES, +CARAVAN_TOOLS, +NUM_CARAVAN_CATEGORIES +}; + +struct defense_game : public special_game +{ + defense_game(); + + virtual special_game_id id() { return SGAME_DEFENSE; }; + virtual void init(game *g); + virtual void per_turn(game *g); + virtual void pre_action(game *g, action_id &act); + virtual void post_action(game *g, action_id act); + virtual void game_over(game *g); + +private: + void init_to_style(defense_style new_style); + void load_style(std::string style_name); + + void setup(); + void refresh_setup(WINDOW *w, int selection); + void init_itypes(game *g); + void init_mtypes(game *g); + void init_constructions(game *g); + void init_recipes(game *g); + void init_map(game *g); + std::vector carvan_items(caravan_category cat); + + void spawn_wave(game *g); + void caravan(game *g); + std::vector pick_monster_wave(game *g); + void spawn_wave_monster(game *g, mtype *type); + + std::string special_wave_message(std::string name); + + +// DATA + int current_wave; + + defense_style style; // What type of game is it? + defense_location location; // Where are we? + + int initial_difficulty; // Total "level" of monsters in first wave + int wave_difficulty; // Increased "level" of monsters per wave + + int time_between_waves; // Cooldown / building / healing time + int waves_between_caravans; // How many waves until we get to trade? + + int initial_cash; // How much cash do we start with? + int cash_per_wave; // How much cash do we get per wave? + int cash_increase; // How much does the above increase per wave? + + bool zombies; + bool specials; + bool spiders; + bool triffids; + bool robots; + bool subspace; + + bool hunger; // Do we hunger? + bool thirst; // Do we thirst? + bool sleep; // Do we need to sleep? + + bool mercenaries; // Do caravans offer the option of hiring a mercenary? + +}; + +#endif diff --git a/itype.h b/itype.h index fa6d8e3113..bca8be4990 100644 --- a/itype.h +++ b/itype.h @@ -62,7 +62,8 @@ itm_wrapper, itm_syringe, itm_rag, itm_fur, itm_leather, itm_superglue, itm_katana, itm_spear_wood, itm_spear_knife, itm_baton, itm_bee_sting, itm_wasp_sting, itm_chitin_piece, itm_biollante_bud, itm_canister_empty, itm_gold, itm_coal, itm_petrified_eye, itm_spiral_stone, itm_rapier, itm_cane, - itm_binoculars, itm_usb_drive, + itm_binoculars, itm_usb_drive, itm_pike, itm_broadsword, itm_mace, + itm_morningstar, itm_pool_cue, itm_pool_ball, itm_candlestick, // Footwear itm_sneakers, itm_boots, itm_boots_steel, itm_boots_winter, itm_mocassins, itm_flip_flops, itm_dress_shoes, itm_heels, @@ -71,6 +72,7 @@ itm_jeans, itm_pants, itm_pants_leather, itm_pants_cargo, itm_pants_army, itm_skirt, // Full-body clothing itm_jumpsuit, itm_dress, itm_armor_chitin, itm_suit, itm_hazmat_suit, + itm_armor_plate, // Torso clothing itm_tshirt, itm_polo_shirt, itm_dress_shirt, itm_tank_top, itm_sweatshirt, itm_sweater, itm_hoodie, itm_jacket_light, itm_jacket_jean, itm_blazer, @@ -89,7 +91,7 @@ itm_glasses_eye, itm_glasses_reading, itm_glasses_safety, itm_goggles_swim, itm_hat_ball, itm_hat_boonie, itm_hat_cotton, itm_hat_knit, itm_hat_hunting, itm_hat_fur, itm_hat_hard, itm_helmet_bike, itm_helmet_skid, itm_helmet_ball, itm_helmet_army, itm_helmet_riot, itm_helmet_motor, itm_helmet_chitin, - itm_tophat, + itm_helmet_plate, itm_tophat, // High-storage itm_backpack, itm_purse, itm_mbag, itm_fanny, itm_holster, itm_bootstrap, // Decorative @@ -230,6 +232,7 @@ IF_AMMO_TEARGAS, // Teargas burst IF_AMMO_SMOKE, // Smoke burst IF_AMMO_TRAIL, // Leaves a trail of smoke IF_AMMO_FLASHBANG, // Disorients and blinds +IF_AMMO_STREAM, // Doesn't stop once it hits a monster NUM_ITEM_FLAGS }; diff --git a/itypedef.cpp b/itypedef.cpp index a6ba37cf26..13a9e84b71 100644 --- a/itypedef.cpp +++ b/itypedef.cpp @@ -106,25 +106,25 @@ DRINK("soup", 15, 60, c_red, itm_can_food, A nutritious and delicious hearty vegetable soup."); DRINK("whiskey", 16, 85, c_brown, itm_bottle_glass, - -12, 4, 0,-12, -2, 5, 20, 30,&iuse::alcohol, ADD_ALCOHOL, "\ + -12, 4, 0,-12, -2, 5, 20, 15,&iuse::alcohol, ADD_ALCOHOL, "\ Made from, by, and for real Southern colonels!"); // NAME RAR PRC COLOR CONTAINER DRINK("vodka", 20, 78, c_ltcyan, itm_bottle_glass, // QUE NUT SPO STM HTH ADD CHG FUN use_func addiction type - -10, 2, 0,-12, -2, 5, 20, 30,&iuse::alcohol, ADD_ALCOHOL, "\ + -10, 2, 0,-12, -2, 5, 20, 15,&iuse::alcohol, ADD_ALCOHOL, "\ In Soviet Russia, vodka drinks you!"); DRINK("rum", 14, 85, c_ltcyan, itm_bottle_glass, - -12, 2, 0,-10, -2, 5, 20, 30,&iuse::alcohol, ADD_ALCOHOL, "\ + -12, 2, 0,-10, -2, 5, 20, 15,&iuse::alcohol, ADD_ALCOHOL, "\ Drinking this might make you feel like a pirate. Or not."); DRINK("tequila", 12, 88, c_brown, itm_bottle_glass, - -12, 2, 0,-12, -2, 6, 20, 35,&iuse::alcohol, ADD_ALCOHOL, "\ + -12, 2, 0,-12, -2, 6, 20, 18,&iuse::alcohol, ADD_ALCOHOL, "\ Don't eat the worm! Wait, there's no worm in this bottle."); DRINK("beer", 60, 35, c_brown, itm_bottle_glass, - 16, 4, 0, -4, -1, 2, 1, 20, &iuse::alcohol, ADD_ALCOHOL, "\ + 16, 4, 0, -4, -1, 2, 1, 10, &iuse::alcohol, ADD_ALCOHOL, "\ Best served cold, in a glass, and with a lime - but you're not that lucky."); DRINK("bleach", 20, 18, c_white, itm_bottle_plastic, @@ -137,11 +137,11 @@ DRINK("ammonia", 24, 30, c_yellow, itm_bottle_plastic, -96, 0, 0, 0, -2, 0, 1,-30,&iuse::blech, ADD_NULL, "\ Don't drink it. Mixing it with bleach produces toxic gas."); -DRINK("mutagen", 8,8000,c_magenta,itm_bottle_glass, +DRINK("mutagen", 8,1000,c_magenta,itm_bottle_glass, 0, 0, 0, 0, -2, 0, 1, 0,&iuse::mutagen_3,ADD_NULL, "\ A rare substance of uncertain origins. Causes you to mutate."); -DRINK("purifier", 12,16000,c_pink, itm_bottle_glass, +DRINK("purifier", 12, 6000,c_pink, itm_bottle_glass, 0, 0, 0, 0, 1, 0, 1, 0,&iuse::purifier, ADD_NULL, "\ A rare stem-cell treatment, which causes mutations and other genetic defects\n\ to fade away."); @@ -462,21 +462,21 @@ A full medical kit, with bandages, anti-biotics, and rapid healing agents.\n\ Used for healing large amounts of damage."); MED("vitamins", 75, 45, c_cyan, itm_null, - PLASTIC, 0, 3, 0, 50, 0,&iuse::none, ADD_NULL, "\ + PLASTIC, 0, 3, 0, 20, 0,&iuse::none, ADD_NULL, "\ Take frequently to improve your immune system."); MED("aspirin", 85, 30, c_cyan, itm_null, - PLASTIC, 0, -1, 0, 50, 0,&iuse::pkill_1, ADD_NULL, "\ + PLASTIC, 0, -1, 0, 20, 0,&iuse::pkill_1, ADD_NULL, "\ Low-grade painkiller. Best taken in pairs."); MED("caffeine pills", 25, 60, c_cyan, itm_null, - PLASTIC, 8, 0, 3, 20, 0,&iuse::caff, ADD_CAFFEINE, "\ + PLASTIC, 8, 0, 3, 10, 0,&iuse::caff, ADD_CAFFEINE, "\ No-doz pills. Useful for staying up all night."); // NAME RAR PRC COLOR TOOL MED("sleeping pills", 15, 50, c_cyan, itm_null, // MATERIAL STM HTH ADD CHG FUN use_func addiction type - PLASTIC, -8, 0, 40, 20, 0,&iuse::sleep, ADD_SLEEP, "\ + PLASTIC, -8, 0, 40, 10, 0,&iuse::sleep, ADD_SLEEP, "\ Prescription sleep aids. Will make you very tired."); MED("iodine tablets", 5,140, c_yellow, itm_null, @@ -494,68 +494,68 @@ Nighttime flu medication. Will halt all flu symptoms for a while, plus make\n\ you sleepy."); MED("inhaler", 14,200, c_ltblue, itm_null, - PLASTIC, 1, 0, 0,100, 0,&iuse::inhaler, ADD_NULL, "\ + PLASTIC, 1, 0, 0, 20, 0,&iuse::inhaler, ADD_NULL, "\ Vital medicine for those with asthma. Those without asthma can use it for a\n\ minor stimulant boost."); // NAME RAR PRC COLOR TOOL MED("codeine", 15,400, c_cyan, itm_null, // MATERIAL STM HTH ADD CHG FUN use_func addiction type - PLASTIC, -2, 0, 10, 20, 10,&iuse::pkill_2, ADD_PKILLER, "\ + PLASTIC, -2, 0, 10, 10, 10,&iuse::pkill_2, ADD_PKILLER, "\ A weak opiate, prescribed for light to moderate pain."); MED("oxycodone", 7,900, c_cyan, itm_null, - PLASTIC, -4, -1, 16, 20, 18,&iuse::pkill_3, ADD_PKILLER, "\ + PLASTIC, -4, -1, 16, 10, 18,&iuse::pkill_3, ADD_PKILLER, "\ A strong opiate, prescribed for moderate to intense pain."); MED("tramadol", 11,300, c_cyan, itm_null, - PLASTIC, 0, 0, 6, 25, 6,&iuse::pkill_l, ADD_PKILLER, "\ + PLASTIC, 0, 0, 6, 10, 6,&iuse::pkill_l, ADD_PKILLER, "\ A long-lasting opiate, prescribed for moderate pain. Its painkiller effects\n\ last for several hours, but are not as strong as oxycodone."); MED("Xanax", 10,600, c_cyan, itm_null, - PLASTIC, -4, 0, 0, 20, 20,&iuse::xanax, ADD_NULL, "\ + PLASTIC, -4, 0, 0, 10, 20,&iuse::xanax, ADD_NULL, "\ Anti-anxiety medication. It will reduce your stimulant level steadily, and\n\ will temporarily cancel the effects of anxiety, like the Hoarder trait."); // NAME RAR PRC COLOR TOOL -MED("Adderall", 10,750, c_cyan, itm_null, +MED("Adderall", 10,450, c_cyan, itm_null, // MATERIAL STM HTH ADD CHG FUN use_func addiction type - PLASTIC, 24, 0, 10, 40, 10,&iuse::none, ADD_SPEED, "\ + PLASTIC, 24, 0, 10, 10, 10,&iuse::none, ADD_SPEED, "\ A strong stimulant prescribed for ADD. It will greatly increase your\n\ stimulant level, but is quite addictive."); MED("Thorazine", 7,500, c_cyan, itm_null, - PLASTIC, 0, 0, 0, 15, 0,&iuse::thorazine, ADD_NULL, "\ + PLASTIC, 0, 0, 0, 10, 0,&iuse::thorazine, ADD_NULL, "\ Anti-psychotic medication. Used to control the symptoms of schizophrenia and\n\ similar ailments. Also popular as a way to come down for a bad trip."); MED("Prozac", 10,650, c_cyan, itm_null, - PLASTIC, -4, 0, 0, 40, 0,&iuse::prozac, ADD_NULL, "\ + PLASTIC, -4, 0, 0, 15, 0,&iuse::prozac, ADD_NULL, "\ A strong anti-depressant. Useful if your morale level is very low."); MED("cigarettes", 90,120, c_dkgray, itm_lighter, - VEGGY, 1, -1, 40, 20, 5,&iuse::cig, ADD_CIG, "\ + VEGGY, 1, -1, 40, 10, 5,&iuse::cig, ADD_CIG, "\ These will boost your dexterity, intelligence, and perception for a short\n\ time. They are quite addictive."); // NAME RAR PRC COLOR -MED("marijuana", 20,180, c_green, itm_lighter, +MED("marijuana", 20,250, c_green, itm_lighter, // MATERIAL STM HTH ADD CHG FUN use_func addiction type - VEGGY, -8, 0, 0, 15, 18,&iuse::weed, ADD_NULL, "\ + VEGGY, -8, 0, 0, 5, 18,&iuse::weed, ADD_NULL, "\ Really useful only for relaxing. Will reduce your attributes and reflexes."); MED("cocaine", 8,420, c_white, itm_null, POWDER, 20, -2, 30, 8, 25,&iuse::coke, ADD_COKE, "\ A strong, illegal stimulant. Highly addictive."); -MED("methamphetamine", 2,400, c_ltcyan, itm_null, +MED("methamphetamine", 2,800, c_ltcyan, itm_null, POWDER, 10, -4, 50, 6, 30,&iuse::meth, ADD_SPEED, "\ A very strong illegal stimulant. Extremely addictive and bad for you, but\n\ also extremely effective in boosting your alertness."); // NAME RAR PRC COLOR -MED("heroin", 1,600, c_brown, itm_syringe, +MED("heroin", 1,1000,c_brown, itm_syringe, // MATERIAL STM HTH ADD CHG FUN use_func addiction type POWDER, -10, -3, 60, 4, 45,&iuse::pkill_4, ADD_PKILLER, "\ A very strong illegal opiate. Unless you have an opiate tolerance, avoid\n\ @@ -709,7 +709,7 @@ it from the manhole is impossible without a crowbar."); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("rock", 40, 0, '*', c_ltgray, STONE, MNULL, // VOL WGT DAM CUT HIT FLAGS - 1, 3, 14, 0, -1, 0, "\ + 1, 3, 12, 0, -3, 0, "\ A rock the size of a baseball. Makes a decent melee weapon, and is also good\n\ for throwing at enemies."); @@ -764,7 +764,7 @@ A large two-handed axe. Makes a good melee weapon, but is a bit slow."); MELEE("nail board", 5, 80,'/', c_ltred, WOOD, MNULL, 6, 6, 16, 6, 1, mfb(IF_STAB), "\ -A long piece of wood with several nails through one end; essentiall a simple\n\ +A long piece of wood with several nails through one end; essentially a simple\n\ mace. Makes a great melee weapon."); MELEE("X-Acto knife", 10, 40,';', c_dkgray, IRON, PLASTIC, @@ -832,7 +832,7 @@ MELEE("machete", 5, 280,'/', c_blue, IRON, MNULL, This huge iron knife makes an excellent melee weapon."); MELEE("katana", 2, 980,'/', c_ltblue, STEEL, MNULL, - 16, 16, 18, 45, 1, mfb(IF_STAB), "\ + 16, 16, 4, 45, 1, mfb(IF_STAB), "\ A rare sword from Japan. Deadly against unarmored targets, and still very\n\ effective against the armored."); @@ -897,7 +897,7 @@ impossible to tell whether they are carved, naturally formed, or some kind of\n\ fossil."); MELEE("rapier", 3, 980,'/', c_ltblue, STEEL, MNULL, - 8, 10, 5, 20, 2, mfb(IF_STAB), "\ + 6, 9, 5, 28, 2, mfb(IF_STAB), "\ Preferred weapon of gentlemen and swashbucklers. Light and quick, it makes\n\ any battle a stylish battle."); @@ -918,6 +918,45 @@ MELEE("USB drive", 5, 100,',', c_white, PLASTIC,MNULL, 0, 0, 0, 0, 0, 0, "\ A USB thumb drive. Useful for holding software."); +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("awl pike", 5,2000,'/', c_ltcyan, IRON, WOOD, +// VOL WGT DAM CUT HIT FLAGS + 14, 18, 8, 60, 2, mfb(IF_SPEAR), "\ +A medieval weapon consisting of a wood shaft, tipped with an iron spike.\n\ +Though large and heavy compared to other spears, its accuracy and damage\n\ +are unparalled."); + +MELEE("broadsword", 30,1200,'/',c_cyan, IRON, MNULL, + 7, 11, 8, 35, 2, mfb(IF_STAB), "\ +An early modern sword seeing use in the 16th, 17th ane 18th centuries.\n\ +Called 'broad' to contrast with the slimmer rapiers."); + +MELEE("mace", 20,1000,'/',c_black, IRON, WOOD, + 10, 18, 36, 0, 1, 0, "\ +A medieval weapon consisting of a wood handle with a heavy iron end. It\n\ +is heavy and slow, but its crushing damage is devastating."); + +MELEE("morningstar", 10,1200,'/',c_black, IRON, WOOD, + 11, 20, 32, 4, 1, mfb(IF_SPEAR), "\ +A medieval weapon consisting of a wood handle with a heavy, spiked iron\n\ +ball on the end. It deals devastating crushing damage, with a small\n\ +amount of piercing to boot."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("pool cue", 4, 80,'/', c_red, WOOD, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 14, 5, 12, 0, 3, 0, "\ +A hard-wood stick designed for hitting colorful balls around a felt\n\ +table. Truly, the coolest of sports."); + +MELEE("pool ball", 40, 30,'*', c_blue, STONE, MNULL, + 1, 3, 12, 0, -3, 0, "\ +A colorful, hard ball. Essentially a rock."); + +MELEE("candlestick", 20,100,'/', c_yellow, SILVER, MNULL, + 1, 5, 12, 0, 1, 0, "\ +A gold candlestick."); + // ARMOR #define ARMOR(name,rarity,price,color,mat1,mat2,volume,wgt,dam,to_hit,\ encumber,dmg_resist,cut_resist,env,warmth,storage,covers,des)\ @@ -1014,6 +1053,10 @@ ARMOR("hazmat suit", 10,1000,C_BODY, PLASTIC, MNULL, A hazardous materials suit. Though quite bulky and cumbersome, wearing it\n\ will provide excellent protection against ambient radiation."); +ARMOR("plate mail", 2, 700,C_BODY, IRON, MNULL, + 70,140, 8, -5, 5, 16, 20, 0, 2, 0, mfb(bp_torso)|mfb(bp_legs), "\ +An extremely heavy ornamental suit of armor."); + // NAME RAR PRC COLOR MAT1 MAT2 ARMOR("t shirt", 80, 80,C_TORSO, COTTON, MNULL, // VOL WGT DAM HIT ENC RES CUT ENV WRM STO COVERS @@ -1288,6 +1331,14 @@ ARMOR("chitinous helmet", 1, 380,C_HAT, FLESH, MNULL, A helmet made from the exoskeletons of insects. Covers the entire head; very\n\ light and durable."); +// NAME RAR PRC COLOR MAT1 MAT2 +ARMOR("great helm", 1,400,C_HAT, IRON, MNULL, +// VOL WGT DAM HIT ENC RES CUT ENV WRM STO COVERS + 20, 15, 10, 0, 4, 10, 15, 1, 1, 0, mfb(bp_head)|mfb(bp_eyes)| + mfb(bp_mouth), "\ +A medieval helmet which provides excellent protection to the entire head, at\n\ +the cost of great encumbrance."); + ARMOR("top hat", 10, 55,C_HAT, PLASTIC, MNULL, 2, 1, -5, 0, 0, 0, 1, 1, 1, 0, mfb(bp_head), "\ The only hat for a gentleman. Look exquisite while laughing in the face\n\ @@ -1355,7 +1406,7 @@ AMMO("batteries", 50, 120,AT_BATT, c_magenta, IRON, A set of universal batteries. Used to charge almost any electronic device.", 0); -AMMO("plutonium cell", 10,3000,AT_PLUT, c_ltgreen, STEEL, +AMMO("plutonium cell", 10,1500,AT_PLUT, c_ltgreen, STEEL, 1, 1, 0, 0, 0, 0, 0, 5, "\ A nuclear-powered battery. Used to charge advanced and rare electronics.", 0); @@ -1370,30 +1421,32 @@ AMMO("BB", 8, 50,AT_BB, c_pink, STEEL, A box of small steel balls. They deal virtually no damage.", 0); -AMMO("wood arrow", 5,500,AT_ARROW, c_green, WOOD, - 2, 60, 16, 3, 10, 14, 0, 15, "\ +// NAME RAR PRC TYPE COLOR MAT +AMMO("wood arrow", 5,100,AT_ARROW, c_green, WOOD, +// VOL WGT DMG AP RNG ACC REC COUNT + 2, 60, 16, 1, 10, 14, 0, 15, "\ A sharpened arrow carved from wood. It's light-weight, does little damage,\n\ and is so-so on accuracy. Stands a good chance of remaining intact once\n\ fired.", 0); -AMMO("carbon fiber arrow",5,500,AT_ARROW, c_green, PLASTIC, - 2, 30, 24, 5, 15, 18, 0, 12, "\ +AMMO("carbon fiber arrow",5,300,AT_ARROW, c_green, PLASTIC, + 2, 30, 24, 2, 15, 18, 0, 12, "\ High-tech carbon fiber shafts and 100 grain broadheads. Very light weight,\n\ fast, and notoriously fragile.", 0); -AMMO("wood crossbow bolt",8,500,AT_BOLT, c_green, WOOD, - 1, 40, 16, 4, 10, 16, 0, 15, "\ +AMMO("wood crossbow bolt",8,100,AT_BOLT, c_green, WOOD, + 1, 40, 16, 1, 10, 16, 0, 15, "\ A sharpened bolt carved from wood. It's lighter than steel bolts, and does\n\ less damage and is less accurate. Stands a good chance of remaining intact\n\ once fired.", 0); // NAME RAR PRC TYPE COLOR MAT -AMMO("steel crossbow bolt",7,900,AT_BOLT, c_green, STEEL, +AMMO("steel crossbow bolt",7,400,AT_BOLT, c_green, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 1, 90, 26, 8, 14, 12, 0, 10, "\ + 1, 90, 26, 3, 14, 12, 0, 10, "\ A sharp bolt made from steel. Deadly in skilled hands. Stands an excellent\n\ chance of remaining intact once fired.", 0); @@ -1561,7 +1614,7 @@ yaw. This causes the round to tumble inside a target, causing significantly\n\ more damage. It is still outdone by shattering rounds.", 0); -AMMO(".223 Remington", 8, 720,AT_223, c_dkgray, STEEL, +AMMO(".223 Remington", 8, 620,AT_223, c_dkgray, STEEL, 2, 2, 36, 1, 24, 13, 30, 40, "\ The .223 rifle round is a civilian variant of the 5.56 NATO round. It is\n\ designed to tumble or fragment inside a target, dealing devastating damage.\n\ @@ -1570,7 +1623,7 @@ The lower pressure of the .223 compared to the 5.56 results in lower accuracy." 0); // NAME RAR PRC TYPE COLOR MAT -AMMO("5.56 NATO", 6, 950,AT_223, c_dkgray, STEEL, +AMMO("5.56 NATO", 6, 650,AT_223, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT 2, 4, 40, 2, 25, 10, 32, 40, "\ This rifle round has enjoyed widespread use in NATO countries, thanks to its\n\ @@ -1578,20 +1631,20 @@ very light weight and high damage. It is designed to shatter inside a\n\ target, inflicting massive damage.", 0); -AMMO("5.56 incendiary", 2,1140,AT_223, c_dkgray, STEEL, +AMMO("5.56 incendiary", 2, 840,AT_223, c_dkgray, STEEL, 2, 4, 28, 7, 25, 11, 32, 30, "\ A variant of the widely-used 5.56 NATO round, incendiary rounds are designed\n\ to burn hotly upon impact, piercing armor and igniting flammable substances.", mfb(IF_AMMO_INCENDIARY)); -AMMO(".270 Winchester", 8, 900,AT_3006, c_dkgray, STEEL, +AMMO(".270 Winchester", 8, 600,AT_3006, c_dkgray, STEEL, 1, 7, 42, 2, 40, 12, 34, 20, "\ Based off the military .30-03 round, the .270 rifle round is compatible with\n\ most guns that fire .30-06 rounds. However, it is designed for hunting, and\n\ is less powerful than the military rounds, with nearly no armor penetration.", 0); -AMMO(".30-06 AP", 4,1050,AT_3006, c_dkgray, STEEL, +AMMO(".30-06 AP", 4, 650,AT_3006, c_dkgray, STEEL, 1, 12, 50, 16, 40, 7, 36, 10, "\ The .30-06 is a very powerful rifle round designed for long-range use. Its\n\ stupendous accuracy and armor piercing capabilities make it one of the most\n\ @@ -1599,21 +1652,21 @@ deadly rounds available, offset only by its drastic recoil and noise.", 0); // NAME RAR PRC TYPE COLOR MAT -AMMO(".30-06 incendiary", 1,1180,AT_3006, c_dkgray, STEEL, +AMMO(".30-06 incendiary", 1, 780,AT_3006, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT 1, 12, 35, 20, 40, 8, 35, 5, "\ A variant of the powerful .30-06 sniper round, incendiary rounds are designed\n\ to burn hotly upon impact, piercing armor and igniting flammable substances.", mfb(IF_AMMO_INCENDIARY)); -AMMO(".308 Winchester", 7, 920,AT_308, c_dkgray, STEEL, +AMMO(".308 Winchester", 7, 620,AT_308, c_dkgray, STEEL, 1, 9, 36, 1, 35, 7, 33, 20, "\ The .308 Winchester is a rifle round, the commercial equivalent of the\n\ military 7.62x51mm round. Its high accuracy and phenominal damage have made\n\ it the most poplar hunting round in the world.", 0); -AMMO("7.62x51mm", 6,1040,AT_308, c_dkgray, STEEL, +AMMO("7.62x51mm", 6, 680,AT_308, c_dkgray, STEEL, 1, 9, 44, 4, 35, 6, 34, 20, "\ The 7.62x51mm largely replaced the .30-06 round as the standard military\n\ rifle round. It is lighter, but offers similar velocities, resulting in\n\ @@ -1621,51 +1674,51 @@ greater accuracy and reduced recoil.", 0); // NAME RAR PRC TYPE COLOR MAT -AMMO("7.62x51mm incendiary",6,1040,AT_308, c_dkgray, STEEL, +AMMO("7.62x51mm incendiary",6, 740,AT_308, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT 1, 9, 30, 12, 32, 6, 32, 10, "\ A variant of the powerful 7.62x51mm round, incendiary rounds are designed\n\ to burn hotly upon impact, piercing armor and igniting flammable substances.", mfb(IF_AMMO_INCENDIARY)); -AMMO("fusion pack", 2,1200,AT_FUSION, c_ltgreen, PLASTIC, +AMMO("fusion pack", 2, 800,AT_FUSION, c_ltgreen, PLASTIC, 1, 2, 12, 6, 20, 4, 0, 20, "\ In the middle of the 21st Century, military powers began to look towards\n\ energy based weapons. The result was the standard fusion pack, capable of\n\ delivering bolts of superheaed gas at near light speed with no recoil.", mfb(IF_AMMO_INCENDIARY)); -AMMO("40mm concussive", 10,800,AT_40MM, c_ltred, STEEL, +AMMO("40mm concussive", 10,400,AT_40MM, c_ltred, STEEL, 1,200, 5, 0, 20, 8, 15, 4, "\ A 40mm grenade with a concussive explosion.", mfb(IF_AMMO_EXPLOSIVE)); // NAME RAR PRC TYPE COLOR MAT -AMMO("40mm frag", 8, 900,AT_40MM, c_ltred, STEEL, +AMMO("40mm frag", 8, 450,AT_40MM, c_ltred, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT 1,220, 5, 0, 20, 8, 15, 4, "\ A 40mm grenade with a small explosion and a high number of damaging fragments.", mfb(IF_AMMO_FRAG)); -AMMO("40mm incendiary", 6,1000,AT_40MM, c_ltred, STEEL, +AMMO("40mm incendiary", 6, 500,AT_40MM, c_ltred, STEEL, 1,200, 5, 0, 20, 8, 15, 4, "\ A 40mm grenade with a small napalm load, designed to create a burst of flame.", mfb(IF_AMMO_NAPALM)); -AMMO("40mm teargas", 5, 900,AT_40MM, c_ltred, STEEL, +AMMO("40mm teargas", 5, 450,AT_40MM, c_ltred, STEEL, 1,210, 5, 0, 20, 8, 15, 4, "\ A 40mm grenade with a teargas load. It will burst in a cloud of highly\n\ incapacitating gas.", mfb(IF_AMMO_TEARGAS)); -AMMO("40mm smoke cover", 4, 750,AT_40MM, c_ltred, STEEL, +AMMO("40mm smoke cover", 4, 350,AT_40MM, c_ltred, STEEL, 1,210, 5, 0, 20, 8, 15, 6, "\ A 40mm grenade with a smoke load. It will burst in a cloud of harmless gas,\n\ and will also leave a streak of smoke cover in its wake.", mfb(IF_AMMO_SMOKE)|mfb(IF_AMMO_TRAIL)); // NAME RAR PRC TYPE COLOR MAT -AMMO("40mm flashbang", 8, 900,AT_40MM, c_ltred, STEEL, +AMMO("40mm flashbang", 8, 400,AT_40MM, c_ltred, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT 1,210, 5, 0, 20, 8, 15, 6, "\ A 40mm grenade with a flashbang load. It will detonate with a blast of light\n\ @@ -1678,7 +1731,7 @@ The Heckler & Koch 12mm projectiles are used in H&K railguns. It's made of a\n\ ferromagnetic metal, probably cobalt.", 0); -AMMO("hydrogen", 8,1200,AT_PLASMA, c_green, STEEL, +AMMO("hydrogen", 8, 800,AT_PLASMA, c_green, STEEL, 10, 25, 35, 3, 8, 4, 0, 25, "\ A canister of hydrogen. With proper equipment, it could be heated to plasma.", mfb(IF_AMMO_INCENDIARY)); @@ -1689,12 +1742,12 @@ mfb(IF_AMMO_INCENDIARY)); count,des,flags) \ index++;itypes.push_back(new it_ammo(index,rarity,price,name,des,'~',\ color,LIQUID,1,1,0,0,0,flags,ammo_type,dmg,AP,accuracy,recoil,range,count)) -FUEL("gasoline", 0, 400, AT_GAS, c_ltred, +FUEL("gasoline", 0, 50, AT_GAS, c_ltred, // DMG AP RNG ACC REC COUNT 0, 0, 4, 0, 0, 200, "\ Gasoline is a highly flammable liquid. When under pressure, it has the\n\ potential for violent explosion.", -mfb(IF_AMMO_FLAME)); +mfb(IF_AMMO_FLAME)|mfb(IF_AMMO_STREAM)); // GUNS // ammo_type matches one of the ammo_types above. @@ -1724,7 +1777,7 @@ Popular among children. It's fairly accurate, but BBs deal nearly no damage.\n\ It could be used to practice your rifle skill up to level 1.", 0); -GUN("crossbow", 2, 500,c_green, IRON, WOOD, +GUN("crossbow", 2,1000,c_green, IRON, WOOD, sk_archery, AT_BOLT, 6, 9, 11, 1, 0, 18, 0, 6, 0, 1, 800, "\ A slow-loading hand weapon that launches bolts. Stronger people can reload\n\ it much faster. Bolts fired from this weapon have a good chance of remaining\n\ @@ -1732,7 +1785,7 @@ intact for re-use.", mfb(IF_STR_RELOAD)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("compound bow", 2, 700,c_yellow, STEEL, PLASTIC, +GUN("compound bow", 2,1400,c_yellow, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD sk_archery, AT_ARROW,12, 8, 8, 1, 0, 16, 0, 6, 0, 1, 100, "\ A bow with wheels that fires high velocity arrows. Weaker people can use\n\ @@ -1740,60 +1793,60 @@ compound bows more easily. Arrows fired from this weapon have a good chance\n\ of remaining intact for re-use.", mfb(IF_STR8_DRAW)|mfb(IF_RELOAD_AND_SHOOT)); -GUN("longbow", 5, 400,c_yellow, WOOD, MNULL, +GUN("longbow", 5, 800,c_yellow, WOOD, MNULL, sk_archery, AT_ARROW,8, 4, 10, 0, 0, 12, 0, 6, 0, 1, 80, "\ A six-foot wooden bow that fires feathered arrows. This takes a fair amount\n\ of strength to draw. Arrows fired from this weapon have a good chance of\n\ remaining intact for re-use.", mfb(IF_STR10_DRAW)|mfb(IF_RELOAD_AND_SHOOT)); -GUN("pipe rifle: .22", 0, 400,c_ltblue, IRON, WOOD, +GUN("pipe rifle: .22", 0, 800,c_ltblue, IRON, WOOD, sk_rifle, AT_22, 9, 13, 10, 2, -2, 15, 2, 6, 0, 1, 250, "\ A home-made rifle. It is simply a pipe attached to a stock, with a hammer to\n\ strike the single round it holds.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("pipe rifle: 9mm", 0, 460,c_ltblue, IRON, WOOD, +GUN("pipe rifle: 9mm", 0, 900,c_ltblue, IRON, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD sk_rifle, AT_9MM, 10, 16, 10, 2, -2, 15, 2, 6, 0, 1, 250, "\ A home-made rifle. It is simply a pipe attached to a stock, with a hammer to\n\ strike the single round it holds.", 0); -GUN("pipe SMG: 9mm", 0, 540,c_ltblue, IRON, WOOD, +GUN("pipe SMG: 9mm", 0, 1050,c_ltblue, IRON, WOOD, sk_smg, AT_9MM, 5, 8, 6, -1, 0, 30, 6, 5, 4, 10, 400, "\ A home-made machine pistol. It features a rudimentary blowback system, which\n\ allows for small bursts.", 0); -GUN("pipe SMG: .45", 0, 575,c_ltblue, IRON, WOOD, +GUN("pipe SMG: .45", 0, 1150,c_ltblue, IRON, WOOD, sk_smg, AT_45, 6, 9, 7, -1, 0, 30, 6, 5, 3, 8, 400, "\ A home-made machine pistol. It features a rudimentary blowback system, which\n\ allows for small bursts.", 0); -GUN("SIG Mosquito", 5, 600,c_dkgray, STEEL, PLASTIC, +GUN("SIG Mosquito", 5,1200,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_22, 1, 6, 9, 1, 1, 28, 4, 8, 0, 10, 350, "\ A popular, very small .22 pistol. \"Ergonomically designed to give the best\n\ shooting experience.\" --SIG Sauer official website", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("S&W 22A", 5, 650,c_dkgray, STEEL, PLASTIC, +GUN("S&W 22A", 5,1250,c_dkgray, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_pistol, AT_22, 1, 10, 9, 1, 1, 25, 5, 7, 0, 10, 300, "\ A popular .22 pistol. \"Ideal for competitive target shooting or recreational\n\ shooting.\" --Smith & Wesson official website", 0); -GUN("Glock 19", 7, 700,c_dkgray, STEEL, PLASTIC, +GUN("Glock 19", 7,1400,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_9MM, 2, 5, 8, 1, 0, 24, 6, 6, 0, 15, 300, "\ Possibly the most popular pistol in existance. The Glock 19 is often derided\n\ for its plastic contruction, but it is easy to shoot.", 0); -GUN("USP 9mm", 6, 780,c_dkgray, STEEL, PLASTIC, +GUN("USP 9mm", 6,1450,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_9MM, 2, 6, 8, 1, -1, 25, 5, 9, 0, 15, 350, "\ A popular 9mm pistol, widely used among law enforcement. Extensively tested\n\ for durability, it has been found to stay accurate even after subjected to\n\ @@ -1801,33 +1854,33 @@ extreme abuse.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("S&W 619", 4, 720,c_dkgray, STEEL, PLASTIC, +GUN("S&W 619", 4,1450,c_dkgray, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_pistol, AT_38, 2, 9, 9, 1, 2, 23, 4, 8, 0, 7, 75, "\ A seven-round .38 revolver sold by Smith & Wesson. It features a fixed rear\n\ sight and a reinforced frame.", mfb(IF_RELOAD_ONE)); -GUN("Taurus Pro .38", 4, 760,c_dkgray, STEEL, PLASTIC, +GUN("Taurus Pro .38", 4,1500,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_38, 2, 6, 8, 1, 1, 22, 6, 7, 0, 10, 350, "\ A popular .38 pistol. Designed with numerous safety features and built from\n\ high-quality, durable materials.", 0); -GUN("SIG Pro .40", 4, 750,c_dkgray, STEEL, PLASTIC, +GUN("SIG Pro .40", 4,1500,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_40, 2, 6, 8, 1, 1, 22, 8, 7, 0, 12, 350, "\ Originally marketed as a lightweight and compact alternative to older SIG\n\ handguns, the Pro .40 is popular among European police forces.", 0); -GUN("S&W 610", 2, 720,c_dkgray, STEEL, WOOD, +GUN("S&W 610", 2,1460,c_dkgray, STEEL, WOOD, sk_pistol, AT_40, 2, 10, 10, 1, 2, 23, 6, 8, 0, 6, 60, "\ The Smith and Wesson 610 is a classic six-shooter revolver chambered for 10mm\n\ rounds, or for S&W's own .40 round.", mfb(IF_RELOAD_ONE)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("Ruger Redhawk", 3, 760,c_dkgray, STEEL, WOOD, +GUN("Ruger Redhawk", 3,1560,c_dkgray, STEEL, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_pistol, AT_44, 2, 12, 10, 1, 2, 21, 6, 8, 0, 6, 80, "\ One of the most powerful handguns in the world when it was released in 1979,\n\ @@ -1835,28 +1888,28 @@ the Redhawk offers very sturdy contruction, with an appearance that is\n\ reminiscent of \"Wild West\" revolvers.", mfb(IF_RELOAD_ONE)); -GUN("Desert Eagle .44", 2, 840,c_dkgray, STEEL, PLASTIC, +GUN("Desert Eagle .44", 2,1750,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_44, 4, 17, 14, 1, 4, 35, 3, 7, 0, 10, 400, "\ One of the most recognizable handguns due to its popularity in movies, the\n\ \"Deagle\" is better known for its menacing appearance than its performace.\n\ It's highly innaccurate, but its heavy weight reduces recoil.", 0); -GUN("USP .45", 6, 800,c_dkgray, STEEL, PLASTIC, +GUN("USP .45", 6,1600,c_dkgray, STEEL, PLASTIC, sk_pistol, AT_45, 2, 7, 9, 1, 1, 25, 8, 9, 0, 12, 350, "\ A popular .45 pistol, widely used among law enforcement. Extensively tested\n\ for durability, it has been found to stay accurate even after subjected to\n\ extreme abuse.", 0); -GUN("M1911", 5, 880,c_ltgray, STEEL, PLASTIC, +GUN("M1911", 5,1680,c_ltgray, STEEL, PLASTIC, sk_pistol, AT_45, 3, 10, 12, 1, 6, 25, 9, 7, 0, 7, 300, "\ The M1911 was the standard-issue sidearm from the US Military for most of the\n\ 20th Century. It remains one of the most popular .45 pistols today.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("FN Five-Seven", 2, 600,c_ltgray, STEEL, PLASTIC, +GUN("FN Five-Seven", 2,1550,c_ltgray, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_pistol, AT_57, 2, 5, 6, 0, 2, 13, 6, 8, 0, 20, 300, "\ Designed to work with FN's proprietary 5.7x28mm round, the Five-Seven is a\n\ @@ -1864,20 +1917,20 @@ lightweight pistol with a very high capacity, best used against armored\n\ opponents.", 0); -GUN("H&K UCP", 2, 620,c_ltgray, STEEL, PLASTIC, +GUN("H&K UCP", 2,1500,c_ltgray, STEEL, PLASTIC, sk_pistol, AT_46, 2, 5, 6, 0, 2, 12, 6, 8, 0, 20, 300, "\ Designed to work with H&K's proprietary 4.6x30mm round, the UCP is a small\n\ pistol with a very high capacity, best used against armored opponents.", 0); -GUN("sawn-off shotgun", 1, 350,c_red, IRON, WOOD, +GUN("sawn-off shotgun", 1, 700,c_red, IRON, WOOD, sk_shotgun, AT_SHOT, 6, 10, 14, 2, 4, 40, 15, 4, 0, 2, 100, "\ The barrels of shotguns are often sawed in half to make it more maneuverable\n\ and concealable. This has the added effect of reducing accuracy greatly.", mfb(IF_RELOAD_ONE)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("single barrel shotgun",1,300,c_red,IRON, WOOD, +GUN("single barrel shotgun",1,600,c_red,IRON, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_shotgun, AT_SHOT,12, 20, 14, 3, 0, 6, 5, 6, 0, 1, 100, "\ An old shotgun, possibly antique. It is little more than a barrel, a wood\n\ @@ -1885,27 +1938,27 @@ stock, and a hammer to strike the cartridge. Its simple design keeps it both\n\ light and accurate.", 0); -GUN("double barrel shotgun",2,580,c_red,IRON, WOOD, +GUN("double barrel shotgun",2,1050,c_red,IRON, WOOD, sk_shotgun, AT_SHOT,12, 26, 15, 3, 0, 7, 4, 7, 2, 2, 100, "\ An old shotgun, possibly antique. It is little more than a pair of barrels,\n\ a wood stock, and a hammer to strike the cartridge.", mfb(IF_RELOAD_ONE)); -GUN("Remington 870", 9,1200,c_red, STEEL, PLASTIC, +GUN("Remington 870", 9,2200,c_red, STEEL, PLASTIC, sk_shotgun, AT_SHOT,16, 30, 17, 3, 5, 10, 0, 8, 3, 6, 100, "\ One of the most popular shotguns on the market, the Remington 870 is used by\n\ hunters and law enforcement agencies alike thanks to its high accuracy and\n\ muzzle velocity.", mfb(IF_RELOAD_ONE)); -GUN("Mossberg 500", 5,1150,c_red, STEEL, PLASTIC, +GUN("Mossberg 500", 5,2250,c_red, STEEL, PLASTIC, sk_shotgun, AT_SHOT,15, 30, 17, 3, 0, 13, -2, 9, 3, 8, 80, "\ The Mossberg 500 is a popular series of pump-action shotguns, often acquired\n\ for military use. It is noted for its high durability and low recoil.", mfb(IF_RELOAD_ONE)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("Saiga-12", 3,1100,c_red, STEEL, PLASTIC, +GUN("Saiga-12", 3,2300,c_red, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_shotgun, AT_SHOT,15, 36, 17, 3, 0, 17, 2, 7, 4, 10, 500, "\ The Saiga-12 shotgun is designed on the same Kalishnikov pattern as the AK47\n\ @@ -1913,14 +1966,14 @@ rifle. It reloads with a magazine, rather than one shell at a time like most\n\ shotguns.", 0); -GUN("American-180", 2, 800,c_cyan, STEEL, MNULL, +GUN("American-180", 2,1600,c_cyan, STEEL, MNULL, sk_smg, AT_22, 12, 23, 11, 0, 2, 20, 0, 6, 20,165, 500, "\ The American-180 is a submachine gun developed in the 1960s which fires .22\n\ LR, unusual for an SMG. Though the round is low-powered, the high rate of\n\ fire and large magazine makes the 180 a formidable weapon.", 0); -GUN("Uzi 9mm", 8, 980,c_cyan, STEEL, MNULL, +GUN("Uzi 9mm", 8,2080,c_cyan, STEEL, MNULL, sk_smg, AT_9MM, 6, 29, 10, 1, 0, 25, -2, 7, 8, 32, 450, "\ The Uzi 9mm has enjoyed immense popularity, selling more units than any other\n\ submachine gun. It is widely used as a personal defense weapon, or as a\n\ @@ -1928,7 +1981,7 @@ primary weapon by elite frontline forces.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("TEC-9", 10, 880,c_cyan, STEEL, MNULL, +GUN("TEC-9", 10,1750,c_cyan, STEEL, MNULL, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_smg, AT_9MM, 5, 12, 9, 1, 3, 24, 0, 6, 6, 32, 400, "\ The TEC-9 is a machine pistol made of cheap polymers and machine stamped\n\ @@ -1936,14 +1989,14 @@ parts. Its rise in popularity among criminals is largely due to its\n\ intimidating looks and low price.", 0); -GUN("Calico M960", 6,1200,c_cyan, STEEL, MNULL, +GUN("Calico M960", 6,2400,c_cyan, STEEL, MNULL, sk_smg, AT_9MM, 7, 19, 9, 1, -3, 28, -4, 6, 12, 50, 500, "\ The Calico M960 is an automatic carbine with a unique circular magazine which\n\ allows for high capacities and reduced recoil.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("H&K MP5", 12,1400,c_cyan, STEEL, PLASTIC, +GUN("H&K MP5", 12,2800,c_cyan, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_smg, AT_9MM, 12, 26, 10, 2, 1, 18, -3, 8, 4, 30, 400, "\ The Heckler & Koch MP5 is one of the most widely-used submachine guns in the\n\ @@ -1951,7 +2004,7 @@ world, and has been adopted by special police forces and militaries alike.\n\ Its high degree of accuracy and low recoil are universally praised.", 0); -GUN("MAC-10", 14, 920,c_cyan, STEEL, MNULL, +GUN("MAC-10", 14,1800,c_cyan, STEEL, MNULL, sk_smg, AT_45, 4, 25, 8, 1, -4, 28, 0, 7, 20, 30, 450, "\ The MAC-10 is a popular machine pistol originally designed for military use.\n\ For many years they were the most inexpensive automatic weapon in the US, and\n\ @@ -1959,7 +2012,7 @@ enjoyed great popularity among criminals less concerned with quality firearms." , 0); -GUN("H&K UMP45", 12,1500,c_cyan, STEEL, PLASTIC, +GUN("H&K UMP45", 12,3000,c_cyan, STEEL, PLASTIC, sk_smg, AT_45, 13, 20, 11, 1, 0, 13, -3, 8, 4, 25, 450, "\ Developed as a successor to the MP5 submachine gun, the UMP45 retains the\n\ earlier model's supreme accuracy and low recoil, but in the higher .45 caliber." @@ -1967,21 +2020,21 @@ earlier model's supreme accuracy and low recoil, but in the higher .45 caliber." 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("TDI Vector", 4,1800,c_cyan, STEEL, PLASTIC, +GUN("TDI Vector", 4,4200,c_cyan, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_smg, AT_45, 13, 20, 9, 0, -2, 15,-14, 7, 8, 30, 450, "\ The TDI Vector is a submachine gun with a unique in-line design which makes\n\ recoil very managable, even in the powerful .45 caliber.", 0); -GUN("FN P90", 7,2000,c_cyan, STEEL, PLASTIC, +GUN("FN P90", 7,4000,c_cyan, STEEL, PLASTIC, sk_smg, AT_57, 14, 22, 10, 1, 0, 22, -8, 8, 15, 50, 500, "\ The first in a new genre of guns, termed \"personal defense weapons.\" FN\n\ designed the P90 to use their proprietary 5.7x28mm ammunition. It is made\n\ for firing bursts managably.", 0); -GUN("H&K MP7", 5,1600,c_cyan, STEEL, PLASTIC, +GUN("H&K MP7", 5,3400,c_cyan, STEEL, PLASTIC, sk_smg, AT_46, 7, 17, 7, 1, 0, 21,-10, 8, 20, 20, 450, "\ Designed by Heckler & Koch as a competitor to the FN P90, as well as a\n\ successor to the extremely popular H&K MP5. Using H&K's proprietary 4.6x30mm\n\ @@ -1989,7 +2042,7 @@ ammunition, it is designed for burst fire.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("Marlin 39A", 14, 800,c_brown,IRON, WOOD, +GUN("Marlin 39A", 14,1600,c_brown,IRON, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_rifle, AT_22, 11, 26, 12, 3, 3, 10, -5, 8, 0, 10, 450, "\ The oldest and longest-produced shoulder firearm in the world. Though it\n\ @@ -1997,21 +2050,21 @@ fires the weak .22 round, it is highly accurate and damaging, and essentially\n\ has no recoil.", 0); -GUN("Ruger 10/22", 12, 820,c_brown,IRON, WOOD, +GUN("Ruger 10/22", 12,1650,c_brown,IRON, WOOD, sk_rifle, AT_22, 11, 23, 12, 3, 0, 8, -5, 8, 0, 10, 500, "\ A popular and highly accurate .22 rifle. At the time of its introduction in\n\ 1964, it was one of the first modern .22 rifles designed for quality, and not\n\ as a gun for children.", 0); -GUN("Browning BLR", 8,1200,c_brown,IRON, WOOD, +GUN("Browning BLR", 8,3500,c_brown,IRON, WOOD, sk_rifle, AT_3006,12, 28, 12, 3, -3, 6, -4, 7, 0, 4, 100, "\ A very popular rifle for hunting and sniping. Its low ammo capacity is\n\ offset by the very powerful .30-06 round it fires.", mfb(IF_RELOAD_ONE)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("Remington 700", 14,1300,c_brown,IRON, WOOD, +GUN("Remington 700", 14,3200,c_brown,IRON, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_rifle, AT_3006,12, 34, 13, 3, 7, 9, -3, 8, 0, 4, 75, "\ A very popular and durable hunting or sniping rifle. Popular among SWAT\n\ @@ -2019,28 +2072,28 @@ and US Marine snipers. Highly damaging, but perhaps not as accurate as the\n\ competing Browning BLR.", mfb(IF_RELOAD_ONE)); -GUN("SKS", 12,1600,c_brown,IRON, WOOD, +GUN("SKS", 12,3000,c_brown,IRON, WOOD, sk_rifle, AT_762, 12, 34, 13, 3, 0, 5, -4, 8, 0, 10, 450, "\ Developed by the Soviets in 1945, this rifle was quickly replaced by the\n\ full-auto AK47. However, due to its superb accuracy and low recoil, this gun\n\ maintains immense popularity.", 0); -GUN("Ruger Mini-14", 12,1650,c_brown,IRON, WOOD, +GUN("Ruger Mini-14", 12,3200,c_brown,IRON, WOOD, sk_rifle, AT_223, 12, 26, 12, 3, 4, 5, -4, 8, 0, 10, 500, "\ A small, lightweight semi-auto carbine designed for military use. Its superb\n\ accuracy and low recoil makes it more suitable than full-auto rifles for some\n\ situations.", 0); -GUN("Savage 111F", 10,1980,c_brown,STEEL, PLASTIC, +GUN("Savage 111F", 10,3280,c_brown,STEEL, PLASTIC, sk_rifle, AT_308, 12, 26, 13, 3, 6, 4,-11, 9, 0, 3, 100, "\ A very accurate rifle chambered for the powerful .308 round. Its very low\n\ ammo capacity is offset by its accuracy and near-complete lack of recoil.", mfb(IF_RELOAD_ONE)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("H&K G3", 15,2550,c_blue, IRON, WOOD, +GUN("H&K G3", 15,5050,c_blue, IRON, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_rifle, AT_308, 16, 40, 13, 2, 8, 10, 4, 8, 7, 20, 550, "\ An early battle rifle developed after the end of WWII. The G3 is designed to\n\ @@ -2048,20 +2101,20 @@ unload large amounts of deadly ammunition, but it is less suitable over long\n\ ranges.", 0); -GUN("H&K G36", 17,2300,c_blue, IRON, PLASTIC, +GUN("H&K G36", 17,5100,c_blue, IRON, PLASTIC, sk_rifle, AT_223, 15, 32, 13, 2, 6, 8, 5, 8, 10, 30, 500, "\ Designed as a replacement for the early H&K G3 battle rifle, the G36 is more\n\ accurate, and uses the much-lighter .223 round, allowing for a higher ammo\n\ capacity.", 0); -GUN("AK-47", 16,2100,c_blue, IRON, WOOD, +GUN("AK-47", 16,4000,c_blue, IRON, WOOD, sk_rifle, AT_762, 16, 38, 14, 2, 0, 11, 4, 9, 4, 30, 475, "\ One of the most recognizable assault rifles ever made, the AK-47 is renowned\n\ for its durability even under the worst conditions.", 0); -GUN("FN FAL", 16,2250,c_blue, IRON, WOOD, +GUN("FN FAL", 16,4500,c_blue, IRON, WOOD, sk_rifle, AT_308, 19, 36, 14, 2, 7, 13, -2, 8, 10, 20, 550, "\ A Belgian-designed battle rifle, the FN FAL is not very accurate for a rifle,\n\ but its high fire rate and powerful .308 ammunition have made it one of the\n\ @@ -2069,7 +2122,7 @@ most widely-used battle rifles in the world.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("Bushmaster ACR", 4,2150,c_blue, STEEL, PLASTIC, +GUN("Bushmaster ACR", 4,4200,c_blue, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_rifle, AT_223, 15, 27, 18, 2, 2, 10, -2, 8, 3, 30, 475, "\ This carbine was developed for military use in the early 21st century. It is\n\ @@ -2077,13 +2130,13 @@ damaging and accurate, though its rate of fire is a bit slower than competing\n\ .223 carbines.", 0); -GUN("AR-15", 9,2200,c_blue, STEEL, PLASTIC, +GUN("AR-15", 9,4000,c_blue, STEEL, PLASTIC, sk_rifle, AT_223, 19, 28, 12, 2, 0, 6, 0, 7, 5, 30, 500, "\ A widely used assault rifle and the father of popular rifles such as the M16.\n\ It is light and accurate, but not very durable.", 0); -GUN("M4A1", 7,2400,c_blue, STEEL, PLASTIC, +GUN("M4A1", 7,4400,c_blue, STEEL, PLASTIC, sk_rifle, AT_223, 14, 24, 13, 2, 4, 7, 2, 6, 5, 30, 475, "\ A popular carbine, long used by the US military. Though accurate, small, and\n\ lightweight, it is infamous for its fragility, particularly in less-than-\n\ @@ -2091,7 +2144,7 @@ ideal terrain.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("FN SCAR-L", 6,2500,c_blue, STEEL, PLASTIC, +GUN("FN SCAR-L", 6,4800,c_blue, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_rifle, AT_223, 15, 29, 18, 2, 1, 6, -4, 8, 6, 30, 500, "\ A modular assault rifle designed for use by US Special Ops units. The 'L' in\n\ @@ -2099,21 +2152,21 @@ its name stands for light, as it uses the lightweight .223 round. It is very\n\ accurate and low on recoil.", 0); -GUN("FN SCAR-H", 5,2750,c_blue, STEEL, PLASTIC, +GUN("FN SCAR-H", 5,4950,c_blue, STEEL, PLASTIC, sk_rifle, AT_308, 16, 32, 20, 2, 1, 8, -4, 8, 5, 20, 550, "\ A modular assault rifle designed for use by US Special Ops units. The 'H' in\n\ its name stands for heavy, as it uses the powerful .308 round. It is fairly\n\ accurate and low on recoil.", 0); -GUN("Steyr AUG", 6,2900,c_blue, STEEL, PLASTIC, +GUN("Steyr AUG", 6,4900,c_blue, STEEL, PLASTIC, sk_rifle, AT_223, 14, 32, 17, 1, -3, 7, -8, 8, 3, 30, 550, "\ The Steyr AUG is an Austrian assault rifle that uses a bullpup design. It is\n\ used in the armed forces and police forces of many nations, and enjoys\n\ low recoil and high accuracy.", 0); -GUN("M249", 1,3500,c_ltred,STEEL, PLASTIC, +GUN("M249", 1,7500,c_ltred,STEEL, PLASTIC, sk_rifle, AT_223, 32, 68, 27, -4, -6, 20, 6, 7, 20,200, 750, "\ The M249 is a mountable machine gun used by the US Military and SWAT teams.\n\ Quite innaccurate and difficult to control, the M249 is designed to fire many\n\ @@ -2122,7 +2175,7 @@ rounds very quickly." 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("V29 laser pistol", 1,3800,c_magenta,STEEL,PLASTIC, +GUN("V29 laser pistol", 1,7200,c_magenta,STEEL,PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_pistol, AT_FUSION,4, 6, 5, 1, -2, 20, 0, 8, 0, 20, 200, "\ The V29 laser pistol was designed in the mid-21st century, and was one of the\n\ @@ -2130,7 +2183,7 @@ first firearms to use fusion as its ammunition. It is larger than most\n\ traditional handguns, but displays no recoil whatsoever.", 0); -GUN("FTK-93 fusion gun", 1,5200,c_magenta,STEEL, PLASTIC, +GUN("FTK-93 fusion gun", 1,9800,c_magenta,STEEL, PLASTIC, sk_rifle, AT_FUSION,18,20, 10, 1, 40, 10, 0, 9, 0, 2, 600, "\ A very powerful fusion rifle developed shortly before the influx of monsters.\n\ It can only hold two rounds at a time, but a special superheating unit causes\n\ @@ -2138,27 +2191,27 @@ its bolts to be extremely deadly.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("simple flamethr.",1,800,c_pink, STEEL, PLASTIC, +GUN("simple flamethr.",1,1600,c_pink, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD sk_shotgun, AT_GAS, 16, 8, 8, -1, -5, 6, 0, 6, 0,800, 800, "\ A simple, home-made flamethrower. While its capacity is not superb, it is\n\ more than capable of igniting terrain and monsters alike.", mfb(IF_FIRE_100)); -GUN("flamethrower", 1,1800,c_pink, STEEL, MNULL, +GUN("flamethrower", 1,3800,c_pink, STEEL, MNULL, sk_shotgun, AT_GAS, 20, 14, 10, -2, 10, 4, 0, 8, 4,1600, 900, "\ A large flamethrower with substantial gas reserves. Very manacing and\n\ deadly.", mfb(IF_FIRE_100)); -GUN("tube 40mm launcher",0, 800,c_ltred,STEEL, WOOD, +GUN("tube 40mm launcher",0, 400,c_ltred,STEEL, WOOD, sk_launcher, AT_40MM,12, 20, 13, -1, 0, 16, 0, 6, 0, 1, 350, "\ A simple, home-made grenade launcher. Basically a tube with a pin firing\n\ mechanism to activate the grenade.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("M79 launcher", 5,2000,c_ltred,STEEL, WOOD, +GUN("M79 launcher", 5,4000,c_ltred,STEEL, WOOD, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD sk_launcher, AT_40MM,14, 24, 16, -1, 3, 4, -5, 8, 0, 1, 250, "\ A widely-used grenade launcher which first saw use by American forces in the\n\ @@ -2166,14 +2219,14 @@ Vietnam war. Though mostly replaced by more modern launchers, the M79 still\n\ sees use with many units worldwide.", 0); -GUN("M320 launcher", 10,4200,c_ltred,STEEL, MNULL, +GUN("M320 launcher", 10,8500,c_ltred,STEEL, MNULL, sk_launcher, AT_40MM, 5, 13, 6, 0, 0, 12, 5, 9, 0, 1, 150, "\ Developed by Heckler & Koch, the M320 grenade launcher has the functionality\n\ of larger launchers in a very small package. However, its smaller size\n\ contributes to a lack of accuracy.", 0); -GUN("Milkor MGL", 6,5200,c_ltred,STEEL, MNULL, +GUN("Milkor MGL", 6,10400,c_ltred,STEEL, MNULL, sk_launcher, AT_40MM, 24, 45, 13, -1, 0, 5, -2, 8, 2, 6, 300, "\ The Milkor Multi-Grenade Launcher is designed to compensate for the drawback\n\ of single-shot grenade launchers by allowing sustained heavy firepower.\n\ @@ -2181,21 +2234,21 @@ However, it is still slow to reload and must be used with careful planning.", mfb(IF_RELOAD_ONE)); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("coilgun", 1, 100,c_ltblue, IRON, MNULL, +GUN("coilgun", 1, 200,c_ltblue, IRON, MNULL, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD sk_pistol, AT_NAIL, 6, 30, 10, -1, 8, 10, 0, 5, 0, 100, 600, "\ A homemade gun, using electromagnets to accelerate a ferromagnetic\n\ projectile to high velocity. Powered by UPS.", mfb(IF_USE_UPS)); -GUN("H&K G80 Railgun", 2,1600,c_ltblue,STEEL, MNULL, +GUN("H&K G80 Railgun", 2,9200,c_ltblue,STEEL, MNULL, sk_rifle, AT_12MM,12, 36, 12, 1, 5, 15, 0, 8, 0, 20, 550, "\ Developed by Heckler & Koch in 2033, the railgun magnetically propels\n\ a ferromagnetic projectile using an alternating current. This makes it\n\ silent while still deadly. Powered by UPS.", mfb(IF_USE_UPS)); -GUN("Boeing XM-P Plasma Rifle", 1,2000,c_ltblue,STEEL, MNULL, +GUN("Boeing XM-P Plasma Rifle", 1,13000,c_ltblue,STEEL, MNULL, sk_rifle, AT_PLASMA,15, 40, 12, 1, 5, 5, 0, 8, 5, 25, 700, "\ Boeing developed the focused plasma weaponry together with DARPA. It heats\n\ hydrogen to create plasma and envelops it with polymers to reduce blooming.\n\ @@ -2932,7 +2985,7 @@ air. They are covered with whirring blades and attack by throwing themselves\n\ against their target. Use this item to activate the manhack."); // NAME RAR PRC SYM COLOR MAT1 MAT -TOOL("inactive turret", 1,2000,';',c_ltgreen, STEEL, PLASTIC, +TOOL("inactive turret", 1,4000,';',c_ltgreen, STEEL, PLASTIC, // VOL WGT DAM CUT HIT MAX DEF USE SEC FUEL REVERT FUNCTION 12, 12, 8, 0, -3, 0, 0, 0, 0, AT_NULL, itm_null, &iuse::turret,0,"\ An inactive turret. Using this item involves turning it on and placing it\n\ diff --git a/keypress.cpp b/keypress.cpp index b35efe93dc..22aa7ea33a 100644 --- a/keypress.cpp +++ b/keypress.cpp @@ -1,4 +1,5 @@ #include "keypress.h" +#include "action.h" long input() { @@ -66,3 +67,46 @@ void get_direction(int &x, int &y, char ch) y = -2; } } + +void get_direction(int &x, int &y, action_id act) +{ + x = 0; + y = 0; + switch (act) { + case ACTION_MOVE_NW: + x = -1; + y = -1; + return; + case ACTION_MOVE_NE: + x = 1; + y = -1; + return; + case ACTION_MOVE_W: + x = -1; + return; + case ACTION_MOVE_S: + y = 1; + return; + case ACTION_MOVE_N: + y = -1; + return; + case ACTION_MOVE_E: + x = 1; + return; + case ACTION_MOVE_SW: + x = -1; + y = 1; + return; + case ACTION_MOVE_SE: + x = 1; + y = 1; + return; + case ACTION_PAUSE: + x = 0; + y = 0; + return; + default: + x = -2; + y = -2; + } +} diff --git a/map.cpp b/map.cpp index e061dc0068..8c32dbcd32 100644 --- a/map.cpp +++ b/map.cpp @@ -187,12 +187,41 @@ bool map::bash(int x, int y, int str, std::string &sound) i--; } } + switch (ter(x, y)) { case t_wall_wood: - if (str >= rng(0, 120)) { + if (str >= rng(0, 120) && str >= rng(0, 120)) { + sound += "crunch!"; + ter(x, y) = t_wall_wood_chipped; + int num_boards = rng(0, 2); + for (int i = 0; i < num_boards; i++) + add_item(x, y, (*itypes)[itm_2x4], 0); + return true; + } else { + sound += "whump!"; + return true; + } + break; + + case t_wall_wood_chipped: + if (str >= rng(0, 100) && str >= rng(0, 100)) { + sound += "crunch!"; + ter(x, y) = t_wall_wood_broken; + int num_boards = rng(3, 8); + for (int i = 0; i < num_boards; i++) + add_item(x, y, (*itypes)[itm_2x4], 0); + return true; + } else { + sound += "whump!"; + return true; + } + break; + + case t_wall_wood_broken: + if (str >= rng(0, 80) && str >= rng(0, 80)) { sound += "crash!"; ter(x, y) = t_dirt; - int num_boards = rng(8, 20); + int num_boards = rng(4, 10); for (int i = 0; i < num_boards; i++) add_item(x, y, (*itypes)[itm_2x4], 0); return true; @@ -201,6 +230,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_door_c: case t_door_locked: case t_door_locked_alarm: @@ -213,6 +243,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_door_b: if (str >= rng(0, 30)) { sound += "crash!"; @@ -226,6 +257,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_window: case t_window_alarm: if (str >= rng(0, 6)) { @@ -237,6 +269,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_door_boarded: if (str >= dice(3, 50)) { sound += "crash!"; @@ -250,6 +283,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_window_boarded: if (str >= dice(3, 30)) { sound += "crash!"; @@ -263,6 +297,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_paper: if (str >= dice(2, 6) - 2) { sound += "rrrrip!"; @@ -273,6 +308,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_toilet: if (str >= dice(8, 4) - 8) { sound += "porcelain breaking!"; @@ -283,6 +319,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_dresser: case t_bookcase: if (str >= dice(3, 45)) { @@ -297,6 +334,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_wall_glass_h: case t_wall_glass_v: case t_wall_glass_h_alarm: @@ -311,6 +349,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_reinforced_glass_h: case t_reinforced_glass_v: if (str >= rng(60, 100)) { @@ -322,6 +361,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_tree_young: if (str >= rng(0, 50)) { sound += "crunch!"; @@ -335,6 +375,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_underbrush: if (str >= rng(0, 30) && !one_in(4)) { sound += "crunch."; @@ -345,6 +386,18 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + + case t_shrub: + if (str >= rng(0, 30) && str >= rng(0, 30) && str >= rng(0, 30) && one_in(2)){ + sound += "crunch."; + ter(x, y) = t_underbrush; + return true; + } else { + sound += "brush."; + return true; + } + break; + case t_marloss: if (str > rng(0, 40)) { sound += "crunch!"; @@ -355,6 +408,7 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } break; + case t_vat: if (str >= dice(2, 20)) { sound += "ker-rash!"; @@ -404,6 +458,7 @@ void map::destroy(game *g, int x, int y, bool makesound) } ter(x, y) = t_rubble; break; + case t_door_c: case t_door_b: case t_door_locked: @@ -416,6 +471,7 @@ void map::destroy(game *g, int x, int y, bool makesound) } } break; + case t_wall_v: case t_wall_h: for (int i = x - 2; i <= x + 2; i++) { @@ -428,6 +484,7 @@ void map::destroy(game *g, int x, int y, bool makesound) } ter(x, y) = t_rubble; break; + default: if (has_flag(explodes, x, y)) g->explosion(x, y, 40, 0, true); @@ -447,6 +504,17 @@ void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) switch (ter(x, y)) { + case t_wall_wood_broken: + case t_door_b: + if (hit_items || one_in(8)) { // 1 in 8 chance of hitting the door + dam -= rng(20, 40); + if (dam > 0) + ter(x, y) = t_dirt; + } else + dam -= rng(0, 1); + break; + + case t_door_c: case t_door_locked: case t_door_locked_alarm: @@ -455,15 +523,6 @@ void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) ter(x, y) = t_door_b; break; - case t_door_b: - if (hit_items || one_in(8)) { // 1 in 8 chance of hitting the door - dam -= rng(10, 30); - if (dam > 0) - ter(x, y) = t_door_frame; - } else - dam -= rng(0, 1); - break; - case t_door_boarded: dam -= rng(15, 35); if (dam > 0) @@ -1807,6 +1866,23 @@ void map::spawn_monsters(game *g) } } +void map::clear_spawns() +{ + for (int i = 0; i < my_MAPSIZE * my_MAPSIZE; i++) + grid[i].spawns.clear(); +} + +void map::clear_traps() +{ + for (int i = 0; i < my_MAPSIZE * my_MAPSIZE; i++) { + for (int x = 0; x < SEEX; x++) { + for (int y = 0; y < SEEY; y++) + grid[i].trp[x][y] = tr_null; + } + } +} + + bool map::inbounds(int x, int y) { return (x >= 0 && x < SEEX * my_MAPSIZE && y >= 0 && y < SEEY * my_MAPSIZE); diff --git a/map.h b/map.h index f05ac3e5e7..24cdc84b45 100644 --- a/map.h +++ b/map.h @@ -43,6 +43,8 @@ class map virtual void load(game *g, int wx, int wy); void shift(game *g, int wx, int wy, int x, int y); void spawn_monsters(game *g); + void clear_spawns(); + void clear_traps(); // Movement and LOS int move_cost(int x, int y); // Cost to move through; 0 = impassible @@ -122,6 +124,8 @@ class map void add_spawn(monster *mon); computer* add_computer(int x, int y, std::string name, int security); + std::vector *itypes; + protected: void saven(overmap *om, unsigned int turn, int x, int y, int gridx, int gridy); bool loadn(game *g, int x, int y, int gridx, int gridy); @@ -143,7 +147,6 @@ class map field nulfield; // Returned when &field_at() is asked for an OOB value int nulrad; // OOB &radiation() - std::vector *itypes; std::vector *traps; std::vector (*mapitems)[num_itloc]; diff --git a/mapdata.h b/mapdata.h index 502c13d1e2..03965a05c7 100644 --- a/mapdata.h +++ b/mapdata.h @@ -35,6 +35,7 @@ enum t_flag { container, // Items on this square are hidden until looted by the player door, // Can be opened--used for NPC pathfinding. flammable, // May be lit on fire + l_flammable, // Harder to light on fire, but still possible explodes, // Explodes when on fire diggable, // Digging monsters, seeding monsters, digging w/ shovel, etc. swimmable, // You (and monsters) swim here @@ -72,7 +73,7 @@ t_grate, t_slime, t_bridge, // Walls & doors -t_wall_half, t_wall_wood, +t_wall_half, t_wall_wood, t_wall_wood_chipped, t_wall_wood_broken, t_wall_v, t_wall_h, t_wall_metal_v, t_wall_metal_h, t_wall_glass_v, t_wall_glass_h, @@ -89,7 +90,7 @@ t_window, t_window_alarm, t_window_empty, t_window_frame, t_window_boarded, t_rock, t_fault, t_paper, // Tree -t_tree, t_tree_young, t_underbrush, +t_tree, t_tree_young, t_underbrush, t_shrub, t_root_wall, t_wax, t_floor_wax, t_fence_v, t_fence_h, @@ -110,6 +111,7 @@ t_radio_tower, t_radio_controls, t_console_broken, t_console, t_sewage_pipe, t_sewage_pump, t_centrifuge, +t_column, // Containers t_fridge, t_dresser, t_rack, t_bookcase, @@ -161,17 +163,22 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! {"sidewalk", '.', c_ltgray, 2, mfb(transparent)}, {"floor", '.', c_cyan, 2, - mfb(transparent)}, + mfb(transparent)|mfb(l_flammable)}, {"metal grate", '#', c_dkgray, 2, mfb(transparent)}, {"slime", '~', c_green, 6, mfb(transparent)|mfb(container)|mfb(flammable)}, {"walkway", '#', c_yellow, 2, mfb(transparent)}, -{"half-built wall", '#', c_brown, 4, +{"half-built wall", '#', c_ltred, 4, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)}, -{"wooden wall", '#', c_brown, 0, +{"wooden wall", '#', c_ltred, 0, + mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, +{"chipped wood wall",'#', c_ltred, 0, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, +{"broken wood wall", '&', c_ltred, 0, + mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)| + mfb(supports_roof)}, {"wall", '|', c_ltgray, 0, mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, {"wall", '-', c_ltgray, 0, @@ -202,14 +209,14 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)| mfb(supports_roof)}, {"open wood door", '\'', c_brown, 2, - mfb(transparent)|mfb(supports_roof)}, + mfb(flammable)|mfb(transparent)|mfb(supports_roof)}, {"closed wood door", '+', c_brown, 0, // Actually locked mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, {"closed wood door", '+', c_brown, 0, // Locked and alarmed mfb(bashable)|mfb(flammable)|mfb(alarmed)|mfb(noitem)| mfb(supports_roof)}, {"empty door frame", '.', c_brown, 2, - mfb(transparent)|mfb(supports_roof)}, + mfb(flammable)|mfb(transparent)|mfb(supports_roof)}, {"boarded up door", '#', c_brown, 0, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, {"closed metal door",'+', c_cyan, 0, @@ -223,7 +230,7 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! {"open glass door", '\'', c_ltcyan, 2, mfb(transparent)|mfb(supports_roof)}, {"bulletin board", '6', c_blue, 0, - mfb(noitem)}, + mfb(flammable)|mfb(noitem)}, {"makeshift portcullis", '&', c_cyan, 0, mfb(noitem)}, {"window", '"', c_ltcyan, 0, @@ -249,15 +256,17 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, {"young tree", '1', c_green, 0, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)}, -{"underbrush", '#', c_green, 6, +{"underbrush", '#', c_ltgreen, 6, mfb(transparent)|mfb(bashable)|mfb(diggable)|mfb(container)|mfb(rough)| mfb(flammable)}, +{"shrub", '#', c_green, 0, + mfb(transparent)|mfb(bashable)|mfb(container)|mfb(flammable)}, {"root wall", '#', c_brown, 0, mfb(noitem)|mfb(supports_roof)}, {"wax wall", '#', c_yellow, 0, mfb(container)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, {"wax floor", '.', c_yellow, 2, - mfb(transparent)}, + mfb(transparent)|mfb(l_flammable)}, {"picket fence", '|', c_brown, 3, mfb(transparent)|mfb(diggable)|mfb(flammable)|mfb(noitem)}, {"picket fence", '-', c_brown, 3, @@ -269,7 +278,7 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! {"marloss bush", '1', c_dkgray, 0, mfb(transparent)|mfb(bashable)|mfb(flammable)}, {"fungal bed", '#', c_dkgray, 3, - mfb(transparent)|mfb(flammable)|mfb(diggable)}, + mfb(transparent)|mfb(l_flammable)|mfb(diggable)}, {"fungal tree", '7', c_dkgray, 0, mfb(flammable)|mfb(noitem)}, {"shallow water", '~', c_ltblue, 5, @@ -283,7 +292,7 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! {"bed", '#', c_magenta, 5, mfb(transparent)|mfb(container)|mfb(flammable)}, {"toilet", '&', c_white, 0, - mfb(transparent)|mfb(bashable)}, + mfb(transparent)|mfb(bashable)|mfb(l_flammable)}, {"sandbox", '#', c_yellow, 3, mfb(transparent)}, {"slide", '#', c_ltcyan, 4, @@ -307,7 +316,7 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! {"blown-out missile",'#', c_ltgray, 0, mfb(noitem)}, {"counter", '#', c_blue, 4, - mfb(transparent)}, + mfb(transparent)|mfb(flammable)}, {"radio tower", '&', c_ltgray, 0, mfb(noitem)}, {"radio controls", '6', c_green, 0, @@ -322,12 +331,14 @@ const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! mfb(noitem)}, {"centrifuge", '{', c_magenta, 0, mfb(transparent)}, +{"column", '1', c_ltgray, 0, + mfb(flammable)}, {"refrigerator", '{', c_ltcyan, 0, mfb(container)}, {"dresser", '{', c_brown, 0, mfb(transparent)|mfb(container)|mfb(flammable)}, {"display rack", '{', c_ltgray, 0, - mfb(transparent)|mfb(container)}, + mfb(transparent)|mfb(container)|mfb(l_flammable)}, {"book case", '{', c_brown, 0, mfb(container)|mfb(flammable)}, {"dumpster", '{', c_green, 0, diff --git a/mapgen.cpp b/mapgen.cpp index 4b558974a6..1bd41663c0 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -42,6 +42,16 @@ enum room_type { room_bunker_rifles, room_bunker_grenades, room_bunker_armor, + room_mansion_courtyard, + room_mansion_entry, + room_mansion_bedroom, + room_mansion_library, + room_mansion_kitchen, + room_mansion_dining, + room_mansion_game, + room_mansion_pool, + room_mansion_bathroom, + room_mansion_gallery, room_split }; @@ -53,6 +63,10 @@ void silo_rooms(map *m); void build_mine_room(map *m, room_type type, int x1, int y1, int x2, int y2); map_extra random_map_extra(map_extras); +room_type pick_mansion_room(int x1, int y1, int x2, int y2); +void build_mansion_room(map *m, room_type type, int x1, int y1, int x2, int y2); +void mansion_room(map *m, int x1, int y1, int x2, int y2); // pick & build + void line(map *m, ter_id type, int x1, int y1, int x2, int y2); void square(map *m, ter_id type, int x1, int y1, int x2, int y2); void rough_circle(map *m, ter_id type, int x, int y, int rad); @@ -4131,8 +4145,10 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, square(this, t_table, 9, 8, 10, 9); square(this, t_table, 14, 8, 15, 9); // Pool tables - square(this, t_pool_table, 4, 13, 8, 14); - square(this, t_pool_table, 13, 13, 17, 14); + square(this, t_pool_table, 4, 13, 8, 14); + place_items(mi_pool_table, 50, 4, 13, 8, 14, false, 0); + square(this, t_pool_table, 13, 13, 17, 14); + place_items(mi_pool_table, 50, 13, 13, 17, 14, false, 0); // 1 in 4 chance to have glass walls in front if (one_in(4)) { line(this, t_wall_glass_h, 3, 1, 5, 1); @@ -4167,7 +4183,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, place_items(mi_snacks, 50, 18, 18, 21, 18, false, 0); place_items(mi_fridgesnacks, 60, 21, 4, 21, 4, false, 0); place_items(mi_fridgesnacks, 60, 21, 17, 21, 17, false, 0); - place_items(mi_alcohol, 50, 21, 5, 21, 8, false, 0); + place_items(mi_alcohol, 70, 21, 5, 21, 8, false, 0); place_items(mi_trash, 15, 2, 17, 16, 19, true, 0); if (terrain_type == ot_bar_east) @@ -4556,6 +4572,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, } } +// Rotate to face the road if (t_east >= ot_road_null && t_east <= ot_bridge_ew) rotate(1); if (t_south >= ot_road_null && t_south <= ot_bridge_ew) @@ -4880,6 +4897,211 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, } break; + case ot_mansion_entrance: { +// Left wall + line(this, t_wall_v, 0, 0, 0, SEEY * 2 - 2); + line(this, t_door_c, 0, SEEY - 1, 0, SEEY); +// Front wall + line(this, t_wall_h, 1, 10, SEEX * 2 - 1, 10); + line(this, t_door_locked, SEEX - 1, 10, SEEX, 10); + int winx1 = rng(2, 4); + int winx2 = rng(4, 6); + line(this, t_window, winx1, 10, winx2, 10); + line(this, t_window, SEEX * 2 - 1 - winx1, 10, SEEX * 2 - 1 - winx2, 10); + winx1 = rng(7, 10); + winx2 = rng(10, 11); + line(this, t_window, winx1, 10, winx2, 10); + line(this, t_window, SEEX * 2 - 1 - winx1, 10, SEEX * 2 - 1 - winx2, 10); + line(this, t_door_c, SEEX - 1, 10, SEEX, 10); +// Bottom wall + line(this, t_wall_h, 0, SEEY * 2 - 1, SEEX * 2 - 1, SEEY * 2 - 1); + line(this, t_door_c, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + + build_mansion_room(this, room_mansion_courtyard, 1, 0, SEEX * 2 - 1, 9); + square(this, t_floor, 1, 11, SEEX * 2 - 1, SEEY * 2 - 2); + build_mansion_room(this, room_mansion_entry, 1, 11, SEEX * 2 - 1, SEEY*2 - 2); +// Rotate to face the road + if (t_east >= ot_road_null && t_east <= ot_bridge_ew) + rotate(1); + if (t_south >= ot_road_null && t_south <= ot_bridge_ew) + rotate(2); + if (t_west >= ot_road_null && t_west <= ot_bridge_ew) + rotate(3); + } break; + + case ot_mansion: +// Start with floors all over + square(this, t_floor, 1, 0, SEEX * 2 - 1, SEEY * 2 - 2); +// We always have a left and bottom wall + line(this, t_wall_v, 0, 0, 0, SEEY * 2 - 2); + line(this, t_wall_h, 0, SEEY * 2 - 1, SEEX * 2 - 1, SEEY * 2 - 1); +// tw and rw are the boundaries of the rooms inside... + tw = 0; + rw = SEEX * 2 - 1; +// ...if we need outside walls, adjust tw & rw and build them +// We build windows below. + if (t_north != ot_mansion_entrance && t_north != ot_mansion) { + tw = 1; + line(this, t_wall_h, 0, 0, SEEX * 2 - 1, 0); + } + if (t_east != ot_mansion_entrance && t_east != ot_mansion) { + rw = SEEX * 2 - 2; + line(this, t_wall_v, SEEX * 2 - 1, 0, SEEX * 2 - 1, SEEX * 2 - 1); + } +// Now pick a random layout + switch (rng(1, 6)) { + + case 1: // Just one. big. room. + mansion_room(this, 1, tw, rw, SEEY * 2 - 2); + if (t_west == ot_mansion_entrance || t_west == ot_mansion) + line(this, t_door_c, 0, SEEY - 1, 0, SEEY); + if (t_south == ot_mansion_entrance || t_south == ot_mansion) + line(this, t_door_c, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + break; + + case 2: // Wide hallway, two rooms. + if (one_in(2)) { // vertical hallway + line(this, t_wall_v, 9, tw, 9, SEEY * 2 - 2); + line(this, t_wall_v, 14, tw, 14, SEEY * 2 - 2); + line(this, t_floor, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + line(this, t_door_c, 0, SEEY - 1, 0, SEEY); + mansion_room(this, 1, tw, 8, SEEY * 2 - 2); + mansion_room(this, 15, tw, rw, SEEY * 2 - 2); + ter( 9, rng(tw + 2, SEEX * 2 - 4)) = t_door_c; + ter(14, rng(tw + 2, SEEX * 2 - 4)) = t_door_c; + } else { // horizontal hallway + line(this, t_wall_h, 1, 9, rw, 9); + line(this, t_wall_h, 1, 14, rw, 14); + line(this, t_door_c, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + line(this, t_floor, 0, SEEY - 1, 0, SEEY); + mansion_room(this, 1, tw, rw, 8); + mansion_room(this, 1, 15, rw, SEEY * 2 - 2); + ter(rng(3, rw - 2), 9) = t_door_c; + ter(rng(3, rw - 2), 14) = t_door_c; + } + if (t_west == ot_mansion_entrance || t_west == ot_mansion) + line(this, t_door_c, 0, SEEY - 1, 0, SEEY); + if (t_south == ot_mansion_entrance || t_south == ot_mansion) + line(this, t_floor, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + break; + + case 3: // Four corners rooms + line(this, t_wall_v, 10, tw, 10, 9); + line(this, t_wall_v, 13, tw, 13, 9); + line(this, t_wall_v, 10, 14, 10, SEEY * 2 - 2); + line(this, t_wall_v, 13, 14, 13, SEEY * 2 - 2); + line(this, t_wall_h, 1, 10, 10, 10); + line(this, t_wall_h, 1, 13, 10, 13); + line(this, t_wall_h, 13, 10, rw, 10); + line(this, t_wall_h, 13, 13, rw, 13); + mansion_room(this, 1, tw, 9, 9); + mansion_room(this, 14, tw, rw, 9); + mansion_room(this, 1, 14, 9, SEEY * 2 - 2); + mansion_room(this, 14, 14, rw, SEEY * 2 - 2); + if (t_west == ot_mansion_entrance || t_west == ot_mansion) + line(this, t_floor, 0, SEEY - 1, 0, SEEY); + if (t_south == ot_mansion_entrance || t_south == ot_mansion) + line(this, t_floor, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + break; + + case 4: // One large room in lower-left + mw = rng( 4, 10); + cw = rng(13, 19); + line(this, t_wall_h, 1, mw, cw, mw); + ter(rng(2, cw - 1), mw) = t_door_c; + line(this, t_wall_v, cw, mw + 1, cw, SEEY * 2 - 2); + ter(cw, rng(mw + 2, SEEY * 2 - 3)) = t_door_c; + mansion_room(this, 1, mw + 1, cw - 1, SEEY * 2 - 2); +// And a couple small rooms in the UL LR corners + x = rng(5, 10); + line(this, t_wall_v, x, tw, x, mw - 1); + mansion_room(this, 1, tw, x - 1, mw - 1); + if (one_in(2)) + ter(rng(2, x - 2), mw) = t_door_c; + else + ter(x, rng(tw + 2, mw - 2)) = t_door_c; + y = rng(13, 18); + line(this, t_wall_h, cw + 1, y, rw, y); + mansion_room(this, cw + 1, y, rw, SEEY * 2 - 2); + if (one_in(2)) + ter(rng(cw + 2, rw - 1), y) = t_door_c; + else + ter(rng(y + 2, SEEY * 2 - 3), cw) = t_door_c; + + if (t_west == ot_mansion_entrance || t_west == ot_mansion) + line(this, t_floor, 0, SEEY - 1, 0, SEEY); + if (t_south == ot_mansion_entrance || t_south == ot_mansion) + line(this, t_floor, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); + break; + } +// Finally, place windows on outside-facing walls if necessary + if (t_west != ot_mansion_entrance && t_west != ot_mansion) { + int consecutive = 0; + for (int i = 1; i < SEEY; i++) { + if (move_cost(1, i) != 0 && move_cost(1, SEEY * 2 - 1 - i) != 0) { + if (consecutive == 3) + consecutive = 0; // No really long windows + else { + consecutive++; + ter(0, i) = t_window; + ter(0, SEEY * 2 - 1 - i) = t_window; + } + } else + consecutive = 0; + } + } + if (t_south != ot_mansion_entrance && t_south != ot_mansion) { + int consecutive = 0; + for (int i = 1; i < SEEX; i++) { + if (move_cost(i, SEEY * 2 - 2) != 0 && + move_cost(SEEX * 2 - 1 - i, SEEY * 2 - 2) != 0) { + if (consecutive == 3) + consecutive = 0; // No really long windows + else { + consecutive++; + ter(i, SEEY * 2 - 1) = t_window; + ter(SEEX * 2 - 1 - i, SEEY * 2 - 1) = t_window; + } + } else + consecutive = 0; + } + } + if (t_east != ot_mansion_entrance && t_east != ot_mansion) { + int consecutive = 0; + for (int i = 1; i < SEEY; i++) { + if (move_cost(SEEX * 2 - 2, i) != 0 && + move_cost(SEEX * 2 - 2, SEEY * 2 - 1 - i) != 0) { + if (consecutive == 3) + consecutive = 0; // No really long windows + else { + consecutive++; + ter(SEEX * 2 - 1, i) = t_window; + ter(SEEX * 2 - 1, SEEY * 2 - 1 - i) = t_window; + } + } else + consecutive = 0; + } + } + + if (t_north != ot_mansion_entrance && t_north != ot_mansion) { + int consecutive = 0; + for (int i = 1; i < SEEX; i++) { + if (move_cost(i, 1) != 0 && move_cost(SEEX * 2 - 1 - i, 1) != 0) { + if (consecutive == 3) + consecutive = 0; // No really long windows + else { + consecutive++; + ter(i, 0) = t_window; + ter(SEEX * 2 - 1 - i, 0) = t_window; + } + } else + consecutive = 0; + } + } + + break; + + case ot_spider_pit_under: for (int i = 0; i < SEEX * 2; i++) { for (int j = 0; j < SEEY * 2; j++) { @@ -7088,6 +7310,292 @@ map_extra random_map_extra(map_extras embellishments) return map_extra(choice); } + +room_type pick_mansion_room(int x1, int y1, int x2, int y2) +{ + int dx = abs(x1 - x2), dy = abs(y1 - y2), area = dx * dy; + int shortest = (dx < dy ? dx : dy), longest = (dx > dy ? dx : dy); + std::vector valid; + if (shortest >= 12) + valid.push_back(room_mansion_courtyard); + if (shortest >= 7 && area >= 64 && area <= 100) + valid.push_back(room_mansion_bedroom); + if (shortest >= 9) + valid.push_back(room_mansion_library); + if (shortest >= 6 && area <= 60) + valid.push_back(room_mansion_kitchen); + if (longest >= 7 && shortest >= 5) + valid.push_back(room_mansion_dining); + if (shortest >= 6 && longest <= 10) + valid.push_back(room_mansion_game); + if (shortest >= 10) + valid.push_back(room_mansion_pool); + if (longest <= 6) + valid.push_back(room_mansion_bathroom); + if (longest >= 8 && shortest <= 6) + valid.push_back(room_mansion_gallery); + + if (valid.empty()) { + debugmsg("x: %d - %d, dx: %d\n\ + y: %d - %d, dy: %d", x1, x2, dx, + y1, y2, dy); + return room_null; + } + + return valid[ rng(0, valid.size() - 1) ]; +} + +void build_mansion_room(map *m, room_type type, int x1, int y1, int x2, int y2) +{ + int dx = abs(x1 - x2), dy = abs(y1 - y2), area = dx * dy; + int cx_low = (x1 + x2) / 2, cx_hi = (x1 + x2 + 1) / 2, + cy_low = (y1 + y2) / 2, cy_hi = (y1 + y2 + 1) / 2; + +/* + debugmsg("\ +x: %d - %d, dx: %d cx: %d/%d\n\ +x: %d - %d, dx: %d cx: %d/%d", x1, x2, dx, cx_low, cx_hi, + y1, y2, dy, cy_low, cy_hi); +*/ + bool walled_west = (x1 == 0), walled_north = (y1 == 0), + walled_east = (x2 == SEEX * 2 - 1), walled_south = (y2 == SEEY * 2 - 1); + + switch (type) { + + case room_mansion_courtyard: + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y++) + m->ter(x, y) = grass_or_dirt(); + } + if (one_in(4)) { // Tree grid + for (int x = 1; x <= dx / 2; x += 4) { + for (int y = 1; y <= dx / 2; y += 4) { + m->ter(x1 + x, y1 + y) = t_tree; + m->ter(x2 - x, y2 - y) = t_tree; + } + } + } + if (one_in(3)) { // shrub-lined + for (int i = x1; i <= x2; i++) { + if (m->ter(i, y2 + 1) != t_door_c) + m->ter(i, y2) = t_shrub; + } + } + break; + + case room_mansion_entry: + if (!one_in(3)) { // Columns + for (int y = y1 + 2; y <= y2; y += 3) { + m->ter(cx_low - 3, y) = t_column; + m->ter(cx_low + 3, y) = t_column; + } + } + if (one_in(6)) { // Suits of armor + int start = y1 + rng(2, 4), end = y2 - rng(0, 4), step = rng(3, 6); + for (int y = start; y <= end; y += step) { + m->add_item(x1 + 1, y, (*(m->itypes))[itm_helmet_plate], 0); + m->add_item(x1 + 1, y, (*(m->itypes))[itm_armor_plate], 0); + if (one_in(2)) + m->add_item(x1 + 1, y, (*(m->itypes))[itm_pike], 0); + else if (one_in(3)) + m->add_item(x1 + 1, y, (*(m->itypes))[itm_broadsword], 0); + else if (one_in(6)) + m->add_item(x1 + 1, y, (*(m->itypes))[itm_mace], 0); + else if (one_in(6)) + m->add_item(x1 + 1, y, (*(m->itypes))[itm_morningstar], 0); + + m->add_item(x2 - 1, y, (*(m->itypes))[itm_helmet_plate], 0); + m->add_item(x2 - 1, y, (*(m->itypes))[itm_armor_plate], 0); + if (one_in(2)) + m->add_item(x2 - 1, y, (*(m->itypes))[itm_pike], 0); + else if (one_in(3)) + m->add_item(x2 - 1, y, (*(m->itypes))[itm_broadsword], 0); + else if (one_in(6)) + m->add_item(x2 - 1, y, (*(m->itypes))[itm_mace], 0); + else if (one_in(6)) + m->add_item(x2 - 1, y, (*(m->itypes))[itm_morningstar], 0); + } + } + break; + + case room_mansion_bedroom: + if (dx > dy || (dx == dy && one_in(2))) { // horizontal + int dressy = (one_in(2) ? cy_low - 2 : cy_low + 2); + if (one_in(2)) { // bed on left + square(m, t_bed, x1 + 1, cy_low - 1, x1 + 3, cy_low + 1); + m->ter(x1 + 1, dressy) = t_dresser; + m->place_items(mi_dresser, 80, x1 + 1, dressy, x1 + 1, dressy, false, 0); + } else { // bed on right + square(m, t_bed, x2 - 3, cy_low - 1, x2 - 1, cy_low + 1); + m->ter(x1 + 1, dressy) = t_dresser; + m->place_items(mi_dresser, 80, x2 - 1, dressy, x2 - 1, dressy, false, 0); + } + } else { // vertical + int dressx = (one_in(2) ? cx_low - 2 : cx_low + 2); + if (one_in(2)) { // bed at top + square(m, t_bed, cx_low - 1, y1 + 1, cx_low + 1, y1 + 3); + m->ter(dressx, y1 + 1) = t_dresser; + m->place_items(mi_dresser, 80, dressx, y1 + 1, dressx, y1 + 1, false, 0); + } else { // bed at bottom + square(m, t_bed, cx_low - 1, y2 - 3, cx_low + 1, y2 - 1); + m->ter(dressx, y2 - 1) = t_dresser; + m->place_items(mi_dresser, 80, dressx, y2 - 1, dressx, y2 - 1, false, 0); + } + } + m->place_items(mi_bedroom, 75, x1, y1, x2, y2, false, 0); + if (one_in(10)) + m->place_items(mi_homeguns, 58, x1, y1, x2, y2, false, 0); + break; + + case room_mansion_library: + if (dx < dy || (dx == dy && one_in(2))) { // vertically-aligned bookshelves + for (int x = x1 + 1; x <= cx_low - 2; x += 3) { + for (int y = y1 + 1; y <= y2 - 3; y += 4) { + square(m, t_bookcase, x, y, x + 1, y + 2); + m->place_items(mi_novels, 85, x, y, x + 1, y + 2, false, 0); + m->place_items(mi_manuals, 62, x, y, x + 1, y + 2, false, 0); + m->place_items(mi_textbooks, 40, x, y, x + 1, y + 2, false, 0); + } + } + for (int x = x2 - 1; x >= cx_low + 2; x -= 3) { + for (int y = y1 + 1; y <= y2 - 3; y += 4) { + square(m, t_bookcase, x - 1, y, x, y + 2); + m->place_items(mi_novels, 85, x - 1, y, x, y + 2, false, 0); + m->place_items(mi_manuals, 62, x - 1, y, x, y + 2, false, 0); + m->place_items(mi_textbooks, 40, x - 1, y, x, y + 2, false, 0); + } + } + } else { // horizontally-aligned bookshelves + for (int y = y1 + 1; y <= cy_low - 2; y += 3) { + for (int x = x1 + 1; x <= x2 - 3; x += 4) { + square(m, t_bookcase, x, y, x + 2, y + 1); + m->place_items(mi_novels, 85, x, y, x + 2, y + 1, false, 0); + m->place_items(mi_manuals, 62, x, y, x + 2, y + 1, false, 0); + m->place_items(mi_textbooks, 40, x, y, x + 2, y + 1, false, 0); + } + } + for (int y = y2 - 1; y >= cy_low + 2; y -= 3) { + for (int x = x1 + 1; x <= x2 - 3; x += 4) { + square(m, t_bookcase, x, y - 1, x + 2, y); + m->place_items(mi_novels, 85, x, y - 1, x + 2, y, false, 0); + m->place_items(mi_manuals, 62, x, y - 1, x + 2, y, false, 0); + m->place_items(mi_textbooks, 40, x, y - 1, x + 2, y, false, 0); + } + } + } + break; + + case room_mansion_kitchen: + square(m, t_counter, cx_low, cy_low, cx_hi, cy_hi); + m->place_items(mi_cleaning, 58, x1 + 1, y1 + 1, x2 - 1, y2 - 1, false, 0); + if (one_in(2)) { // Fridge/racks on left/right + line(m, t_fridge, cx_low - 1, cy_low, cx_low - 1, cy_hi); + m->place_items(mi_fridge, 82, cx_low - 1, cy_low, cx_low - 1, cy_hi, + false, 0); + line(m, t_rack, cx_hi + 1, cy_low, cx_hi + 1, cy_hi); + m->place_items(mi_cannedfood, 50, cx_hi + 1, cy_low, cx_hi + 1, cy_hi, + false, 0); + m->place_items(mi_pasta, 50, cx_hi + 1, cy_low, cx_hi + 1, cy_hi, + false, 0); + } else { // Fridge/racks on top/bottom + line(m, t_fridge, cx_low, cy_low - 1, cx_hi, cy_low - 1); + m->place_items(mi_fridge, 82, cx_low, cy_low - 1, cx_hi, cy_low - 1, + false, 0); + line(m, t_rack, cx_low, cy_hi + 1, cx_hi, cy_hi + 1); + m->place_items(mi_cannedfood, 50, cx_low, cy_hi + 1, cx_hi, cy_hi + 1, + false, 0); + m->place_items(mi_pasta, 50, cx_low, cy_hi + 1, cx_hi, cy_hi + 1, + false, 0); + } + break; + + case room_mansion_dining: + if (dx < dy || (dx == dy && one_in(2))) { // vertically-aligned table + line(m, t_table, cx_low, y1 + 2, cx_low, y2 - 2); + line(m, t_bench, cx_low - 1, y1 + 2, cx_low - 1, y2 - 2); + line(m, t_bench, cx_low + 1, y1 + 2, cx_low + 1, y2 - 2); + m->place_items(mi_dining, 78, cx_low, y1 + 2, cx_low, y2 - 2, false, 0); + } else { // horizontally-aligned table + line(m, t_table, x1 + 2, cy_low, x2 - 2, cy_low); + line(m, t_bench, x1 + 2, cy_low - 1, x2 - 2, cy_low - 1); + line(m, t_bench, x1 + 2, cy_low + 1, x2 - 2, cy_low + 1); + m->place_items(mi_dining, 78, x1 + 2, cy_low, x2 - 2, cy_low, false, 0); + } + break; + + case room_mansion_game: + if (dx < dy || one_in(2)) { // vertically-aligned table + square(m, t_pool_table, cx_low, cy_low - 1, cx_low + 1, cy_low + 1); + m->place_items(mi_pool_table, 80, cx_low, cy_low - 1, cx_low + 1, cy_low + 1, + false, 0); + } else { // horizontally-aligned table + square(m, t_pool_table, cx_low - 1, cy_low, cx_low + 1, cy_low + 1); + m->place_items(mi_pool_table, 80, cx_low - 1, cy_low, cx_low + 1, cy_low + 1, + false, 0); + } + break; + + case room_mansion_pool: + square(m, t_water_sh, x1 + 2, y1 + 2, x2 - 2, y2 - 2); + break; + + case room_mansion_bathroom: + m->ter( rng(x1 + 1, x2 - 1), rng(y1 + 1, y2 - 1) ) = t_toilet; + m->place_items(mi_harddrugs, 20, x1 + 1, y1 + 1, x2 - 1, y2 - 1, false, 0); + m->place_items(mi_softdrugs, 72, x1 + 1, y1 + 1, x2 - 1, y2 - 1, false, 0); + m->place_items(mi_cleaning, 48, x1 + 1, y1 + 1, x2 - 1, y2 - 1, false, 0); + break; + + case room_mansion_gallery: + for (int x = x1 + 1; x <= cx_low - 1; x += rng(2, 4)) { + for (int y = y1 + 1; y <= cy_low - 1; y += rng(2, 4)) { + if (one_in(10)) { // Suit of armor + m->add_item(x, y, (*(m->itypes))[itm_helmet_plate], 0); + m->add_item(x, y, (*(m->itypes))[itm_armor_plate], 0); + if (one_in(2)) + m->add_item(x, y, (*(m->itypes))[itm_pike], 0); + else if (one_in(3)) + m->add_item(x, y, (*(m->itypes))[itm_broadsword], 0); + else if (one_in(6)) + m->add_item(x, y, (*(m->itypes))[itm_mace], 0); + else if (one_in(6)) + m->add_item(x, y, (*(m->itypes))[itm_morningstar], 0); + } else { // Objets d'art + m->ter(x, y) = t_counter; + m->place_items(mi_art, 70, x, y, x, y, false, 0); + } + } + } + for (int x = x2 - 1; x >= cx_hi + 1; x -= rng(2, 4)) { + for (int y = y2 - 1; y >= cy_hi + 1; y -= rng(2, 4)) { + if (one_in(10)) { // Suit of armor + m->add_item(x, y, (*(m->itypes))[itm_helmet_plate], 0); + m->add_item(x, y, (*(m->itypes))[itm_armor_plate], 0); + if (one_in(2)) + m->add_item(x, y, (*(m->itypes))[itm_pike], 0); + else if (one_in(3)) + m->add_item(x, y, (*(m->itypes))[itm_broadsword], 0); + else if (one_in(6)) + m->add_item(x, y, (*(m->itypes))[itm_mace], 0); + else if (one_in(6)) + m->add_item(x, y, (*(m->itypes))[itm_morningstar], 0); + } else { // Objets d'art + m->ter(x, y) = t_counter; + m->place_items(mi_art, 70, x, y, x, y, false, 0); + } + } + } + break; + + } +} + +void mansion_room(map *m, int x1, int y1, int x2, int y2) +{ + room_type type = pick_mansion_room(x1, y1, x2, y2); + build_mansion_room(m, type, x1, y1, x2, y2); +} + void map::add_extra(map_extra type, game *g) { item body; diff --git a/mapitems.h b/mapitems.h index 777102f155..fbedd3c3b9 100644 --- a/mapitems.h +++ b/mapitems.h @@ -5,7 +5,7 @@ enum items_location { mi_field, mi_forest, mi_hive, mi_hive_center, mi_road, mi_livingroom, mi_kitchen, mi_fridge, mi_home_hw, mi_bedroom, mi_homeguns, - mi_dresser, + mi_dresser, mi_dining, mi_snacks, mi_fridgesnacks, mi_behindcounter, mi_magazines, mi_softdrugs, mi_harddrugs, mi_cannedfood, mi_pasta, mi_produce, @@ -14,6 +14,7 @@ enum items_location { mi_consumer_electronics, mi_sports, mi_camping, mi_allsporting, mi_alcohol, + mi_pool_table, mi_trash, mi_ammo, mi_pistols, mi_shotguns, mi_rifles, mi_smg, mi_assault, mi_allguns, mi_gunxtras, @@ -22,7 +23,7 @@ enum items_location { mi_cop_weapons, mi_cop_evidence, mi_hospital_lab, mi_hospital_samples, mi_surgery, mi_office, mi_vault, - mi_pawn, mi_mil_surplus, + mi_art, mi_pawn, mi_mil_surplus, mi_shelter, mi_chemistry, mi_teleport, mi_goo, mi_cloning_vat, mi_dissection, mi_hydro, mi_electronics, mi_monparts, mi_bionics, mi_bionics_common, diff --git a/mapitemsdef.cpp b/mapitemsdef.cpp index d0db35a53b..a4aab7a0cb 100644 --- a/mapitemsdef.cpp +++ b/mapitemsdef.cpp @@ -93,6 +93,11 @@ void game::init_mapitems() itm_sewing_kit, itm_flashlight, itm_suit, itm_tophat, itm_glasses_monocle, NULL); + setvector( + mapitems[mi_dining], + itm_wrapper, itm_knife_butter, itm_knife_steak, itm_bottle_glass, + NULL); + setvector( mapitems[mi_snacks], itm_chips, itm_pretzels, itm_chocolate, itm_jerky, itm_candy, itm_tea_raw, @@ -213,6 +218,10 @@ void game::init_mapitems() mapitems[mi_alcohol], itm_whiskey, itm_vodka, itm_rum, itm_tequila, NULL); + setvector( + mapitems[mi_pool_table], + itm_pool_cue, itm_pool_ball, NULL); + setvector( mapitems[mi_trash], itm_iodine, itm_meth, itm_heroin, itm_wrapper, itm_string_6, itm_chain, @@ -376,6 +385,11 @@ void game::init_mapitems() itm_purifier, itm_plut_cell, itm_ftk93, itm_canister_goo, itm_UPS_off, itm_gold, itm_bionics_super, NULL); + setvector( + mapitems[mi_art], + itm_fur, itm_katana, itm_petrified_eye, itm_spiral_stone, itm_rapier, + itm_cane, itm_candlestick, itm_heels, itm_ring, itm_necklace, NULL); + setvector( mapitems[mi_pawn], itm_cigar, itm_katana, itm_gold, itm_rapier, itm_cane, itm_suit, @@ -393,6 +407,7 @@ void game::init_mapitems() itm_novel_romance, itm_novel_spy, itm_novel_scifi, itm_novel_drama, itm_SICP, itm_textbook_robots, itm_extinguisher, itm_radio, itm_chainsaw_off, itm_jackhammer, itm_ring, itm_necklace, itm_usb_drive, + itm_broadsword, itm_morningstar, itm_helmet_plate, NULL); setvector( diff --git a/melee.cpp b/melee.cpp index 116640de9e..943324446d 100644 --- a/melee.cpp +++ b/melee.cpp @@ -268,7 +268,7 @@ int player::hit_mon(game *g, monster *z) } else { dam += stabdam; cutting_penalty = weapon.damage_cut() * 3 + z_armor_stab * 8 - - dice(sklevel[sk_cutting], 10); + dice(sklevel[sk_stabbing], 10); } } diff --git a/monattack.cpp b/monattack.cpp index 44c501f0aa..146a9df837 100644 --- a/monattack.cpp +++ b/monattack.cpp @@ -1137,9 +1137,9 @@ void mattack::smg(game *g, monster *z) fire_t = t; } } + z->sp_timeout = z->type->sp_freq; // Reset timer if (target == NULL) // Couldn't find any targets! return; - z->sp_timeout = z->type->sp_freq; // Reset timer z->moves = -150; // It takes a while if (g->u_see(z->posx, z->posy, t)) g->add_msg("The %s fires its smg!", z->name().c_str()); @@ -1172,7 +1172,7 @@ void mattack::smg(game *g, monster *z) if (!z->has_effect(ME_TARGETED)) { g->sound(z->posx, z->posy, 6, "beep-beep-beep!"); - z->add_effect(ME_TARGETED, 3); + z->add_effect(ME_TARGETED, 8); return; } @@ -1255,7 +1255,7 @@ void mattack::multi_robot(game *g, monster *z) void mattack::ratking(game *g, monster *z) { - if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 4) + if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 10) return; z->sp_timeout = z->type->sp_freq; // Reset timer @@ -1269,3 +1269,10 @@ void mattack::ratking(game *g, monster *z) g->u.add_disease(DI_RAT, 20, g); } + +void mattack::generator(game *g, monster *z) +{ + g->sound(z->posx, z->posy, 100, ""); + if (int(g->turn) % 10 == 0 && z->hp < z->type->hp) + z->hp++; +} diff --git a/monattack.h b/monattack.h index 1de778b06d..fe4f2cf29c 100644 --- a/monattack.h +++ b/monattack.h @@ -41,6 +41,7 @@ class mattack void copbot (game *g, monster *z); void multi_robot (game *g, monster *z); // Pick from tazer, smg, flame void ratking (game *g, monster *z); + void generator (game *g, monster *z); }; #endif diff --git a/mondeath.cpp b/mondeath.cpp index 7400c8ba88..12c09dc44d 100644 --- a/mondeath.cpp +++ b/mondeath.cpp @@ -201,6 +201,8 @@ void mdeath::guilt(game *g, monster *z) return; // We don't give a shit! if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 1) return; // Too far away, we can deal with it + if (z->hp >= 0) + return; // It probably didn't die from damage g->add_msg("You feel terrible for killing %s!", z->name().c_str()); g->u.add_morale(MORALE_KILLED_MONSTER, -50, -250); } @@ -289,3 +291,9 @@ void mdeath::ratking(game *g, monster *z) { g->u.rem_disease(DI_RAT); } + +void mdeath::gameover(game *g, monster *z) +{ + g->add_msg("Your %s is destroyed! GAME OVER!", z->name().c_str()); + g->u.hp_cur[hp_torso] = 0; +} diff --git a/mondeath.h b/mondeath.h index 5284b9faf9..0792d36e22 100644 --- a/mondeath.h +++ b/mondeath.h @@ -26,6 +26,8 @@ class mdeath void thing (game *g, monster *z); // Turn into a full thing void explode (game *g, monster *z); // Damaging explosion void ratking (game *g, monster *z); // Cure verminitis + + void gameover (game *g, monster *z); // Game over! Defense mode }; #endif diff --git a/mongroup.h b/mongroup.h index 1b73a1af63..150a78d8f5 100644 --- a/mongroup.h +++ b/mongroup.h @@ -20,6 +20,9 @@ enum moncat_id { mcat_lab, mcat_nether, mcat_spiral, + mcat_vanilla_zombie, // Defense mode only + mcat_spider, // Defense mode only + mcat_robot, // Defense mode only num_moncats }; diff --git a/mongroupdef.cpp b/mongroupdef.cpp index ea8fab44b3..7afcef562d 100644 --- a/mongroupdef.cpp +++ b/mongroupdef.cpp @@ -7,7 +7,6 @@ void game::init_moncats() moncats[mcat_forest], mon_squirrel, mon_rabbit, mon_deer, mon_wolf, mon_bear, mon_spider_wolf, mon_spider_jumping, mon_dog, NULL); - setvector( moncats[mcat_ant], mon_ant_larva, mon_ant, mon_ant_soldier, mon_ant_queen, NULL); @@ -24,8 +23,7 @@ void game::init_moncats() mon_zombie_necro, mon_boomer, mon_skeleton, NULL); setvector( moncats[mcat_triffid], - mon_triffid, mon_triffid_young, mon_triffid_queen, mon_creeper_hub, - mon_creeper_vine, mon_biollante, mon_triffid_heart, NULL); + mon_triffid, mon_triffid_young, mon_triffid_queen, NULL); setvector( moncats[mcat_fungi], mon_fungaloid, mon_fungaloid_dormant, mon_ant_fungus, mon_zombie_fungus, @@ -55,5 +53,16 @@ void game::init_moncats() setvector( moncats[mcat_spiral], mon_human_snail, mon_twisted_body, mon_vortex, NULL); + setvector( + moncats[mcat_vanilla_zombie], + mon_zombie, NULL); + setvector( + moncats[mcat_spider], + mon_spider_wolf, mon_spider_web, mon_spider_jumping, mon_spider_widow, + NULL); + setvector( + moncats[mcat_robot], + mon_manhack, mon_skitterbot, mon_secubot, mon_copbot, mon_molebot, + mon_tripod, mon_chickenbot, mon_tankbot, NULL); } diff --git a/monmove.cpp b/monmove.cpp index f532eada15..cbb2dbd78c 100644 --- a/monmove.cpp +++ b/monmove.cpp @@ -209,6 +209,8 @@ void monster::move(game *g) int mondex = (plans.size() > 0 ? g->mon_at(plans[0].x, plans[0].y) : -1); monster_attitude current_attitude = attitude(); + if (friendly == 0) + current_attitude = attitude(&(g->u)); // If our plans end in a player, set our attitude to consider that player if (plans.size() > 0) { if (plans.back().x == g->u.posx && plans.back().y == g->u.posy) diff --git a/monster.cpp b/monster.cpp index a31f2a5539..7c246830c9 100644 --- a/monster.cpp +++ b/monster.cpp @@ -260,7 +260,7 @@ void monster::load_info(std::string data, std::vector *mtypes) dump << data; dump >> idtmp >> posx >> posy >> wandx >> wandy >> wandf >> moves >> speed >> hp >> sp_timeout >> plansize >> friendly >> faction_id >> mission_id >> - dead; + dead >> anger >> morale; type = (*mtypes)[idtmp]; point ptmp; for (int i = 0; i < plansize; i++) { @@ -275,7 +275,8 @@ std::string monster::save_info() pack << int(type->id) << " " << posx << " " << posy << " " << wandx << " " << wandy << " " << wandf << " " << moves << " " << speed << " " << hp << " " << sp_timeout << " " << plans.size() << " " << friendly << " " << - faction_id << " " << mission_id << " " << dead; + faction_id << " " << mission_id << " " << dead << " " << anger << + " " << morale; for (int i = 0; i < plans.size(); i++) { pack << " " << plans[i].x << " " << plans[i].y; } @@ -322,7 +323,7 @@ monster_attitude monster::attitude(player *u) if (has_effect(ME_RUN)) return MATT_FLEE; - int effective_anger = anger; + int effective_anger = anger; int effective_morale = morale; if (u != NULL) { @@ -361,8 +362,8 @@ monster_attitude monster::attitude(player *u) void monster::process_triggers(game *g) { - anger += trigger_sum(g, &(type->anger)); - anger -= trigger_sum(g, &(type->placate)); + anger += trigger_sum(g, &(type->anger)); + anger -= trigger_sum(g, &(type->placate)); if (morale < 0) { if (morale < type->morale && one_in(20)) morale++; @@ -393,31 +394,37 @@ int monster::trigger_sum(game *g, std::vector *triggers) int ret = 0; bool check_terrain = false, check_meat = false, check_fire = false; for (int i = 0; i < triggers->size(); i++) { + switch ((*triggers)[i]) { case MTRIG_TIME: if (one_in(20)) ret++; break; + case MTRIG_MEAT: - check_meat = true; check_terrain = true; + check_meat = true; break; + case MTRIG_PLAYER_CLOSE: - if (rl_dist(posx, posy, g->u.posx, g->u.posy) <= 3) + if (rl_dist(posx, posy, g->u.posx, g->u.posy) <= 5) ret += 5; for (int i = 0; i < g->active_npc.size(); i++) { - if (rl_dist(posx, posy, g->active_npc[i].posx, g->active_npc[i].posy) <= 3) + if (rl_dist(posx, posy, g->active_npc[i].posx, g->active_npc[i].posy) <= 5) ret += 5; } break; + case MTRIG_FIRE: - check_meat = true; check_terrain = true; + check_fire = true; break; + case MTRIG_PLAYER_WEAK: if (g->u.hp_percentage() <= 70) - ret += 10 - int(g->u.hp_percentage()); + ret += 10 - int(g->u.hp_percentage() / 10); break; + default: break; // The rest are handled when the impetus occurs } @@ -533,9 +540,10 @@ void monster::hit_monster(game *g, int i) bool monster::hurt(int dam) { hp -= dam; - process_trigger(MTRIG_HURT, int(dam / 8)); if (hp < 1) return true; + if (dam > 0) + process_trigger(MTRIG_HURT, 1 + int(dam / 3)); return false; } @@ -731,7 +739,7 @@ void monster::process_effects(game *g) if (g->debugmon) debugmsg("Duration %d", effects[i].duration); } - if (effects[i].duration <= 0) { + if (effects[i].duration == 0) { if (g->debugmon) debugmsg("Deleting"); effects.erase(effects.begin() + i); diff --git a/mtype.h b/mtype.h index 0eeef6a612..5ace36d1cd 100644 --- a/mtype.h +++ b/mtype.h @@ -75,6 +75,8 @@ mon_eyebot, mon_manhack, mon_skitterbot, mon_secubot, mon_copbot, mon_molebot, mon_tripod, mon_chickenbot, mon_tankbot, mon_turret, mon_exploder, // Hallucinations mon_hallu_zom, mon_hallu_bee, mon_hallu_ant, mon_hallu_mom, +// Special monsters +mon_generator, num_monsters }; @@ -98,6 +100,7 @@ MTRIG_HURT, // We are hurt MTRIG_FIRE, // Fire nearby MTRIG_FRIEND_DIED, // A monster of the same type died MTRIG_FRIEND_ATTACKED, // A monster of the same type attacked +MTRIG_SOUND, // Heard a sound N_MONSTER_TRIGGERS }; @@ -167,8 +170,8 @@ struct mtype { unsigned char frequency; // How often do these show up? 0 (never) to ?? int difficulty;// Used all over; 30 min + (diff-3)*30 min = earlist appearance - signed char agro; // How likely to attack; -5 to 5 - signed char morale; // Default morale level + int agro; // How likely to attack; -100 to 100 + int morale; // Default morale level unsigned int speed; // Speed; human = 100 unsigned char melee_skill; // Melee skill; should be 0 to 5 diff --git a/mtypedef.cpp b/mtypedef.cpp index ce2ad43f55..507d53bfe9 100644 --- a/mtypedef.cpp +++ b/mtypedef.cpp @@ -22,24 +22,24 @@ void game::init_mtypes () #define mon(name, species, sym, color, size, mat, \ freq, diff, agro, morale, speed, melee_skill, melee_dice,\ melee_sides, melee_cut, dodge, arm_bash, arm_cut, item_chance, HP,\ - sp_freq, death, sp_att, desc)\ + sp_freq, death, sp_att, desc) \ id++;\ mtypes.push_back(new mtype(id, name, species, sym, color, size, mat,\ freq, diff, agro, morale, speed, melee_skill, melee_dice, melee_sides,\ melee_cut, dodge, arm_bash, arm_cut, item_chance, HP, sp_freq, death, sp_att,\ desc)) -#define FLAGS(...) setvector(mtypes[id]->flags, __VA_ARGS__, NULL) -#define ANGER(...) setvector(mtypes[id]->anger, __VA_ARGS__, NULL) -#define FEARS(...) setvector(mtypes[id]->fear, __VA_ARGS__, NULL) +#define FLAGS(...) setvector(mtypes[id]->flags, __VA_ARGS__, NULL) +#define ANGER(...) setvector(mtypes[id]->anger, __VA_ARGS__, NULL) +#define FEARS(...) setvector(mtypes[id]->fear, __VA_ARGS__, NULL) #define PLACATE(...) setvector(mtypes[id]->placate, __VA_ARGS__, NULL) // PLEASE NOTE: The description is AT MAX 4 lines of 46 characters each. // FOREST ANIMALS mon("squirrel", species_mammal, 'r', c_ltgray, MS_TINY, FLESH, -// frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 50, 0, -5, -1,140, 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, +// frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq + 50, 0,-99, -8,140, 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, &mdeath::normal, &mattack::none, "\ A small woodland animal." ); @@ -47,7 +47,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_ANIMAL, MF_WARM, MF_FUR); mon("rabbit", species_mammal, 'r', c_white, MS_TINY, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 10, 0, -5, -1,180, 0, 0, 0, 0, 6, 0, 0, 0, 4, 0, + 10, 0,-99, -7,180, 0, 0, 0, 0, 6, 0, 0, 0, 4, 0, &mdeath::normal, &mattack::none, "\ A cute wiggling nose, cotton tail, and\n\ delicious flesh." @@ -56,7 +56,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_ANIMAL, MF_WARM, MF_FUR); mon("deer", species_mammal, 'd', c_brown, MS_LARGE, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 3, 1, -5, -1,300, 4, 3, 3, 0, 3, 0, 0, 0, 80, 0, + 3, 1,-99, -5,300, 4, 3, 3, 0, 3, 0, 0, 0, 80, 0, &mdeath::normal, &mattack::none, "\ A large buck, fast-moving and strong." ); @@ -69,7 +69,7 @@ mon("wolf", species_mammal, 'w', c_dkgray, MS_MEDIUM, FLESH, A vicious and fast pack hunter." ); FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_ANIMAL, MF_WARM, MF_FUR, MF_HIT_AND_RUN); -ANGER(MTRIG_TIME, MTRIG_PLAYER_WEAK); +ANGER(MTRIG_TIME, MTRIG_PLAYER_WEAK, MTRIG_HURT); PLACATE(MTRIG_MEAT); mon("bear", species_mammal, 'B', c_dkgray, MS_LARGE, FLESH, @@ -94,7 +94,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_ANIMAL, MF_WARM, MF_FUR, MF_HIT_AND_RUN); // INSECTOIDS mon("ant larva",species_insect, 'a', c_white, MS_SMALL, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 1, 0, 5, 10, 65, 4, 1, 3, 0, 0, 0, 0, 0, 10, 0, + 1, 0, -1, 10, 65, 4, 1, 3, 0, 0, 0, 0, 0, 10, 0, &mdeath::normal, &mattack::none, "\ The size of a large cat, this pulsating mass\n\ of glistening white flesh turns your stomach." @@ -170,6 +170,7 @@ An evil-looking, slender-bodied wasp with\n\ a vicious sting on its abdomen." ); FLAGS(MF_SMELLS, MF_POISON, MF_VENOM, MF_FLIES); +ANGER(MTRIG_FRIEND_DIED, MTRIG_PLAYER_CLOSE, MTRIG_SOUND); // GIANT WORMS @@ -268,7 +269,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_STUMBLES, MF_WARM, MF_BASHES, MF_POISON); mon("zombie hulk",species_zombie, 'Z', c_blue, MS_HUGE, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 1, 50,150,100,130, 9, 4, 8, 0, 0, 12, 8, 80,260, 0, + 1, 50,100,100,130, 9, 4, 8, 0, 0, 12, 8, 80,260, 0, &mdeath::normal, &mattack::none, "\ A zombie that has somehow grown to the size of\n\ 6 men, with arms as wide as a trash can." @@ -571,7 +572,7 @@ FLAGS(MF_SEES, MF_SMELLS, MF_HEARS, MF_WARM, MF_SWIMS, MF_ANIMAL, MF_FUR); mon("rat king",species_mammal, 'S', c_dkgray, MS_MEDIUM, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 0, 18, 10,100, 40, 4, 1, 3, 1, 0, 0, 0, 0,120, 3, + 0, 18, 10,100, 40, 4, 1, 3, 1, 0, 0, 0, 0,220, 3, &mdeath::ratking, &mattack::ratking, "\ A group of several rats, their tails\n\ knotted together in a filthy mass. A wave\n\ @@ -853,14 +854,14 @@ FLAGS(MF_SEES, MF_WARM, MF_FLIES, MF_FIREY); mon("kreck", species_nether, 'h', c_ltred, MS_SMALL, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 9, 5,100,100,135, 6, 2, 2, 1, 5, 0, 5, 0, 35, 0, + 9, 6,100,100,105, 6, 1, 3, 1, 5, 0, 5, 0, 35, 0, &mdeath::melt, &mattack::none, "\ A small humanoid, the size of a dog, with\n\ twisted red flesh and a distended neck. It\n\ scampers across the ground, panting and\n\ grunting." ); -FLAGS(MF_SEES, MF_SMELLS, MF_HEARS, MF_WARM, MF_BASHES); +FLAGS(MF_SEES, MF_SMELLS, MF_HEARS, MF_WARM, MF_BASHES, MF_HIT_AND_RUN); mon("blank body",species_nether, 'h', c_white, MS_MEDIUM, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq @@ -1037,11 +1038,21 @@ FLAGS(MF_SMELLS); mon("your mother",species_hallu, '@', c_white, MS_MEDIUM, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 0, 0,100,100,100, 3, 0, 0, 0, 0, 0, 0, 0, 1, 20, + 0, 0,100,100,100, 3, 0, 0, 0, 0, 0, 0, 0, 5, 20, &mdeath::guilt, &mattack::disappear, "\ Mom?" ); FLAGS(MF_SEES, MF_HEARS, MF_SMELLS); + +mon("generator", species_none, 'G', c_white, MS_LARGE, STEEL, +// frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq + 0, 0, 0, 0,100, 0, 0, 0, 0, 0, 2, 2, 0,500, 1, + &mdeath::gameover, &mattack::generator, "\ +Your precious generator, noisily humming\n\ +away. Defend it at all costs!" +); +FLAGS(MF_NOHEAD, MF_ACIDPROOF, MF_IMMOBILE); + } diff --git a/mutation_data.cpp b/mutation_data.cpp index 372fa405e3..208f269d8c 100644 --- a/mutation_data.cpp +++ b/mutation_data.cpp @@ -483,8 +483,8 @@ PF_TAIL_FIN, PF_DEFORMED, PF_THIRST, PF_WEBBED, PF_SLIMY, PF_COLDBLOOD2, NULL); case MUTCAT_BEAST: setvector(ret, -PF_TOUGH, PF_DEFT, PF_ANIMALEMPATH, PF_TERRIFYING, PF_ADRENALINE, PF_MYOPIC, -PF_FORGETFUL, PF_NIGHTVISION2, PF_FANGS, PF_FUR, PF_CLAWS, PF_PHEROMONE_MAMMAL, +PF_DEFT, PF_ANIMALEMPATH, PF_TERRIFYING, PF_ADRENALINE, PF_MYOPIC, PF_FORGETFUL, +PF_NIGHTVISION2, PF_FANGS, PF_FUR, PF_CLAWS, PF_PHEROMONE_MAMMAL, PF_PADDED_FEET, PF_TAIL_FLUFFY, PF_SMELLY2, PF_DEFORMED2, PF_HUNGER, PF_TROGLO, PF_CARNIVORE, PF_SNARL, PF_SHOUT3, PF_CANINE_EARS, NULL); break; diff --git a/newcharacter.cpp b/newcharacter.cpp index e18eac22a1..0b8c144aa1 100644 --- a/newcharacter.cpp +++ b/newcharacter.cpp @@ -516,8 +516,12 @@ int set_traits(WINDOW* w, player *u, int &points) traitmin = 1; traitmax = PF_SPLIT; mvwprintz(w, 3, 40, c_ltgray, " "); - mvwprintz(w, 3, 40, COL_TR_GOOD, "%s costs %d points", - traits[cur_adv].name.c_str(), traits[cur_adv].points); + if (traits[cur_adv].points > 0) + mvwprintz(w, 3, 40, COL_TR_GOOD, "%s costs %d points", + traits[cur_adv].name.c_str(), traits[cur_adv].points); + else + mvwprintz(w, 3, 40, COL_TR_GOOD, "%s earns %d points", + traits[cur_adv].name.c_str(), traits[cur_adv].points * -1); mvwprintz(w, 22, 0, COL_TR_GOOD, "%s", traits[cur_adv].description.c_str()); } else { col_on = COL_TR_BAD_ON; diff --git a/omdata.h b/omdata.h index a40c4ab824..821000243e 100644 --- a/omdata.h +++ b/omdata.h @@ -14,7 +14,8 @@ #define OMAPY 180 #define TUTORIAL_Z 10 -#define NETHER_Z 20 +#define DEFENSE_Z 20 +#define NETHER_Z 30 class overmap; @@ -37,7 +38,7 @@ const map_extras field_extras( const map_extras subway_extras( // %%% HEL MIL SCI STA DRG SUP PRT MIN WLF PUD CRT FUM 1WY 75, 0, 5, 12, 5, 5, 0, 7, 0, 0,120, 0, 20, 1); -const map_extras building_extras( +const map_extras build_extras( 90, 0, 5, 12, 0, 10, 0, 5, 5, 0, 0, 60, 8, 1); enum oter_id { @@ -85,6 +86,7 @@ enum oter_id { ot_mil_surplus_west, ot_megastore_entrance, ot_megastore, ot_hospital_entrance, ot_hospital, + ot_mansion_entrance, ot_mansion, // Goodies/dungeons ot_shelter, ot_shelter_under, ot_lab, ot_lab_stairs, ot_lab_core, ot_lab_finale, @@ -170,84 +172,86 @@ const oter_t oterlist[num_ter_types] = { {"river bank", 'R', c_ltblue, 1, no_extras, false, false}, {"river bank", 'R', c_ltblue, 1, no_extras, false, false}, {"river bank", 'R', c_ltblue, 1, no_extras, false, false}, -{"house", '^', c_ltgreen, 5, building_extras, false, false}, -{"house", '>', c_ltgreen, 5, building_extras, false, false}, -{"house", 'v', c_ltgreen, 5, building_extras, false, false}, -{"house", '<', c_ltgreen, 5, building_extras, false, false}, -{"house", '^', c_ltgreen, 5, building_extras, false, false}, -{"house", '>', c_ltgreen, 5, building_extras, false, false}, -{"house", 'v', c_ltgreen, 5, building_extras, false, false}, -{"house", '<', c_ltgreen, 5, building_extras, false, false}, -{"parking lot", 'O', c_dkgray, 1, building_extras, false, false}, -{"park", 'O', c_green, 2, building_extras, false, false}, -{"gas station", '^', c_ltblue, 5, building_extras, false, false}, -{"gas station", '>', c_ltblue, 5, building_extras, false, false}, -{"gas station", 'v', c_ltblue, 5, building_extras, false, false}, -{"gas station", '<', c_ltblue, 5, building_extras, false, false}, -{"pharmacy", '^', c_ltred, 5, building_extras, false, false}, -{"pharmacy", '>', c_ltred, 5, building_extras, false, false}, -{"pharmacy", 'v', c_ltred, 5, building_extras, false, false}, -{"pharmacy", '<', c_ltred, 5, building_extras, false, false}, -{"grocery store", '^', c_green, 5, building_extras, false, false}, -{"grocery store", '>', c_green, 5, building_extras, false, false}, -{"grocery store", 'v', c_green, 5, building_extras, false, false}, -{"grocery store", '<', c_green, 5, building_extras, false, false}, -{"hardware store", '^', c_cyan, 5, building_extras, false, false}, -{"hardware store", '>', c_cyan, 5, building_extras, false, false}, -{"hardware store", 'v', c_cyan, 5, building_extras, false, false}, -{"hardware store", '<', c_cyan, 5, building_extras, false, false}, -{"electronics store", '^', c_yellow, 5, building_extras, false, false}, -{"electronics store", '>', c_yellow, 5, building_extras, false, false}, -{"electronics store", 'v', c_yellow, 5, building_extras, false, false}, -{"electronics store", '<', c_yellow, 5, building_extras, false, false}, -{"sporting goods store",'^', c_ltcyan, 5, building_extras, false, false}, -{"sporting goods store",'>', c_ltcyan, 5, building_extras, false, false}, -{"sporting goods store",'v', c_ltcyan, 5, building_extras, false, false}, -{"sporting goods store",'<', c_ltcyan, 5, building_extras, false, false}, -{"liquor store", '^', c_magenta, 5, building_extras, false, false}, -{"liquor store", '>', c_magenta, 5, building_extras, false, false}, -{"liquor store", 'v', c_magenta, 5, building_extras, false, false}, -{"liquor store", '<', c_magenta, 5, building_extras, false, false}, -{"gun store", '^', c_red, 5, building_extras, false, false}, -{"gun store", '>', c_red, 5, building_extras, false, false}, -{"gun store", 'v', c_red, 5, building_extras, false, false}, -{"gun store", '<', c_red, 5, building_extras, false, false}, -{"clothing store", '^', c_blue, 5, building_extras, false, false}, -{"clothing store", '>', c_blue, 5, building_extras, false, false}, -{"clothing store", 'v', c_blue, 5, building_extras, false, false}, -{"clothing store", '<', c_blue, 5, building_extras, false, false}, -{"library", '^', c_brown, 5, building_extras, false, false}, -{"library", '>', c_brown, 5, building_extras, false, false}, -{"library", 'v', c_brown, 5, building_extras, false, false}, -{"library", '<', c_brown, 5, building_extras, false, false}, -{"subway station", 'S', c_yellow, 5, building_extras, true, false}, -{"subway station", 'S', c_yellow, 5, building_extras, true, false}, -{"subway station", 'S', c_yellow, 5, building_extras, true, false}, -{"subway station", 'S', c_yellow, 5, building_extras, true, false}, -{"police station", '^', c_dkgray, 5, building_extras, false, false}, -{"police station", '>', c_dkgray, 5, building_extras, false, false}, -{"police station", 'v', c_dkgray, 5, building_extras, false, false}, -{"police station", '<', c_dkgray, 5, building_extras, false, false}, +{"house", '^', c_ltgreen, 5, build_extras, false, false}, +{"house", '>', c_ltgreen, 5, build_extras, false, false}, +{"house", 'v', c_ltgreen, 5, build_extras, false, false}, +{"house", '<', c_ltgreen, 5, build_extras, false, false}, +{"house", '^', c_ltgreen, 5, build_extras, false, false}, +{"house", '>', c_ltgreen, 5, build_extras, false, false}, +{"house", 'v', c_ltgreen, 5, build_extras, false, false}, +{"house", '<', c_ltgreen, 5, build_extras, false, false}, +{"parking lot", 'O', c_dkgray, 1, build_extras, false, false}, +{"park", 'O', c_green, 2, build_extras, false, false}, +{"gas station", '^', c_ltblue, 5, build_extras, false, false}, +{"gas station", '>', c_ltblue, 5, build_extras, false, false}, +{"gas station", 'v', c_ltblue, 5, build_extras, false, false}, +{"gas station", '<', c_ltblue, 5, build_extras, false, false}, +{"pharmacy", '^', c_ltred, 5, build_extras, false, false}, +{"pharmacy", '>', c_ltred, 5, build_extras, false, false}, +{"pharmacy", 'v', c_ltred, 5, build_extras, false, false}, +{"pharmacy", '<', c_ltred, 5, build_extras, false, false}, +{"grocery store", '^', c_green, 5, build_extras, false, false}, +{"grocery store", '>', c_green, 5, build_extras, false, false}, +{"grocery store", 'v', c_green, 5, build_extras, false, false}, +{"grocery store", '<', c_green, 5, build_extras, false, false}, +{"hardware store", '^', c_cyan, 5, build_extras, false, false}, +{"hardware store", '>', c_cyan, 5, build_extras, false, false}, +{"hardware store", 'v', c_cyan, 5, build_extras, false, false}, +{"hardware store", '<', c_cyan, 5, build_extras, false, false}, +{"electronics store", '^', c_yellow, 5, build_extras, false, false}, +{"electronics store", '>', c_yellow, 5, build_extras, false, false}, +{"electronics store", 'v', c_yellow, 5, build_extras, false, false}, +{"electronics store", '<', c_yellow, 5, build_extras, false, false}, +{"sporting goods store",'^', c_ltcyan, 5, build_extras, false, false}, +{"sporting goods store",'>', c_ltcyan, 5, build_extras, false, false}, +{"sporting goods store",'v', c_ltcyan, 5, build_extras, false, false}, +{"sporting goods store",'<', c_ltcyan, 5, build_extras, false, false}, +{"liquor store", '^', c_magenta, 5, build_extras, false, false}, +{"liquor store", '>', c_magenta, 5, build_extras, false, false}, +{"liquor store", 'v', c_magenta, 5, build_extras, false, false}, +{"liquor store", '<', c_magenta, 5, build_extras, false, false}, +{"gun store", '^', c_red, 5, build_extras, false, false}, +{"gun store", '>', c_red, 5, build_extras, false, false}, +{"gun store", 'v', c_red, 5, build_extras, false, false}, +{"gun store", '<', c_red, 5, build_extras, false, false}, +{"clothing store", '^', c_blue, 5, build_extras, false, false}, +{"clothing store", '>', c_blue, 5, build_extras, false, false}, +{"clothing store", 'v', c_blue, 5, build_extras, false, false}, +{"clothing store", '<', c_blue, 5, build_extras, false, false}, +{"library", '^', c_brown, 5, build_extras, false, false}, +{"library", '>', c_brown, 5, build_extras, false, false}, +{"library", 'v', c_brown, 5, build_extras, false, false}, +{"library", '<', c_brown, 5, build_extras, false, false}, +{"subway station", 'S', c_yellow, 5, build_extras, true, false}, +{"subway station", 'S', c_yellow, 5, build_extras, true, false}, +{"subway station", 'S', c_yellow, 5, build_extras, true, false}, +{"subway station", 'S', c_yellow, 5, build_extras, true, false}, +{"police station", '^', c_dkgray, 5, build_extras, false, false}, +{"police station", '>', c_dkgray, 5, build_extras, false, false}, +{"police station", 'v', c_dkgray, 5, build_extras, false, false}, +{"police station", '<', c_dkgray, 5, build_extras, false, false}, {"bank", '^', c_ltgray, 5, no_extras, false, false}, {"bank", '>', c_ltgray, 5, no_extras, false, false}, {"bank", 'v', c_ltgray, 5, no_extras, false, false}, {"bank", '<', c_ltgray, 5, no_extras, false, false}, -{"bar", '^', c_pink, 5, building_extras, false, false}, -{"bar", '>', c_pink, 5, building_extras, false, false}, -{"bar", 'v', c_pink, 5, building_extras, false, false}, -{"bar", '<', c_pink, 5, building_extras, false, false}, -{"pawn shop", '^', c_white, 5, building_extras, false, false}, -{"pawn shop", '>', c_white, 5, building_extras, false, false}, -{"pawn shop", 'v', c_white, 5, building_extras, false, false}, -{"pawn shop", '<', c_white, 5, building_extras, false, false}, -{"mil. surplus", '^', c_white, 5, building_extras, false, false}, -{"mil. surplus", '>', c_white, 5, building_extras, false, false}, -{"mil. surplus", 'v', c_white, 5, building_extras, false, false}, -{"mil. surplus", '<', c_white, 5, building_extras, false, false}, -{"megastore", 'M', c_ltblue, 5, building_extras, false, false}, -{"megastore", 'M', c_blue, 5, building_extras, false, false}, -{"hospital", 'H', c_ltred, 5, building_extras, false, false}, -{"hospital", 'H', c_red, 5, building_extras, false, false}, +{"bar", '^', c_pink, 5, build_extras, false, false}, +{"bar", '>', c_pink, 5, build_extras, false, false}, +{"bar", 'v', c_pink, 5, build_extras, false, false}, +{"bar", '<', c_pink, 5, build_extras, false, false}, +{"pawn shop", '^', c_white, 5, build_extras, false, false}, +{"pawn shop", '>', c_white, 5, build_extras, false, false}, +{"pawn shop", 'v', c_white, 5, build_extras, false, false}, +{"pawn shop", '<', c_white, 5, build_extras, false, false}, +{"mil. surplus", '^', c_white, 5, build_extras, false, false}, +{"mil. surplus", '>', c_white, 5, build_extras, false, false}, +{"mil. surplus", 'v', c_white, 5, build_extras, false, false}, +{"mil. surplus", '<', c_white, 5, build_extras, false, false}, +{"megastore", 'M', c_ltblue, 5, build_extras, false, false}, +{"megastore", 'M', c_blue, 5, build_extras, false, false}, +{"hospital", 'H', c_ltred, 5, build_extras, false, false}, +{"hospital", 'H', c_red, 5, build_extras, false, false}, +{"mansion", 'M', c_ltgreen, 5, build_extras, false, false}, +{"mansion", 'M', c_green, 5, build_extras, false, false}, {"evac shelter", '+', c_white, 2, no_extras, true, false}, {"evac shelter", '+', c_white, 2, no_extras, false, true}, {"science lab", 'L', c_ltblue, 5, no_extras, false, false}, @@ -257,7 +261,7 @@ const oter_t oterlist[num_ter_types] = { {"nuclear plant", 'P', c_ltgreen, 5, no_extras, false, false}, {"nuclear plant", 'P', c_ltgreen, 5, no_extras, false, false}, {"military bunker", 'B', c_dkgray, 2, no_extras, true, true}, -{"military outpost", 'M', c_dkgray, 2, building_extras, false, false}, +{"military outpost", 'M', c_dkgray, 2, build_extras, false, false}, {"missile silo", '0', c_ltgray, 2, no_extras, false, false}, {"missile silo", '0', c_ltgray, 2, no_extras, false, false}, {"strange temple", 'T', c_magenta, 5, no_extras, true, false}, @@ -438,16 +442,16 @@ const overmap_special overmap_specials[NUM_OMSPECS] = { &omspec_place::land, mfb(OMS_FLAG_ROAD)}, // Terrain MIN MAX DISTANCE -{ot_bunker, 2, 30, 4, -1, mcat_null, 0, 0, 0, 0, +{ot_bunker, 2, 10, 4, -1, mcat_null, 0, 0, 0, 0, &omspec_place::land, mfb(OMS_FLAG_ROAD)}, {ot_outpost, 0, 10, 4, -1, mcat_null, 0, 0, 0, 0, &omspec_place::wilderness, 0}, -{ot_silo, 0, 2, 30, -1, mcat_null, 0, 0, 0, 0, +{ot_silo, 0, 1, 30, -1, mcat_null, 0, 0, 0, 0, &omspec_place::wilderness, mfb(OMS_FLAG_ROAD)}, -{ot_radio_tower, 5,100, 0, 20, mcat_null, 0, 0, 0, 0, +{ot_radio_tower, 1, 5, 0, 20, mcat_null, 0, 0, 0, 0, &omspec_place::by_highway, 0}, {ot_megastore_entrance, 0, 5, 0, 10, mcat_null, 0, 0, 0, 0, @@ -456,7 +460,7 @@ const overmap_special overmap_specials[NUM_OMSPECS] = { {ot_hospital_entrance, 1, 5, 3, 15, mcat_null, 0, 0, 0, 0, &omspec_place::by_highway, mfb(OMS_FLAG_3X3_SECOND)}, -{ot_sewage_treatment, 1, 10, 10, 20, mcat_null, 0, 0, 0, 0, +{ot_sewage_treatment, 1, 5, 10, 20, mcat_null, 0, 0, 0, 0, &omspec_place::land, mfb(OMS_FLAG_PARKING_LOT)}, {ot_mine_entrance, 0, 5, 15, -1, mcat_null, 0, 0, 0, 0, @@ -469,13 +473,13 @@ const overmap_special overmap_specials[NUM_OMSPECS] = { {ot_spider_pit, 0,500, 0, -1, mcat_null, 0, 0, 0, 0, &omspec_place::forest, 0}, -{ot_slimepit, 0, 10, 0, -1, mcat_goo, 2, 10, 100, 200, +{ot_slimepit, 0, 4, 0, -1, mcat_goo, 2, 10, 100, 200, &omspec_place::land, 0}, -{ot_fungal_bloom, 0, 5, 5, -1, mcat_fungi, 600, 1200, 30, 50, +{ot_fungal_bloom, 0, 3, 5, -1, mcat_fungi, 600, 1200, 30, 50, &omspec_place::wilderness, 0}, -{ot_triffid_grove, 0, 8, 0, -1, mcat_triffid, 800, 1300, 12, 20, +{ot_triffid_grove, 0, 4, 0, -1, mcat_triffid, 800, 1300, 12, 20, &omspec_place::forest, 0}, {ot_river_center, 0, 10, 10, -1, mcat_null, 0, 0, 0, 0, @@ -488,7 +492,7 @@ const overmap_special overmap_specials[NUM_OMSPECS] = { {ot_cave, 0, 30, 0, -1, mcat_null, 0, 0, 0, 0, &omspec_place::wilderness, 0}, -{ot_toxic_dump, 0, 10, 15, -1, mcat_null, 0, 0, 0, 0, +{ot_toxic_dump, 0, 5, 15, -1, mcat_null, 0, 0, 0, 0, &omspec_place::wilderness,0} }; diff --git a/output.cpp b/output.cpp index d83c12e101..8bf474e6d9 100644 --- a/output.cpp +++ b/output.cpp @@ -637,6 +637,51 @@ void popup(const char *mes, ...) refresh(); } +void popup_nowait(const char *mes, ...) +{ + va_list ap; + va_start(ap, mes); + char buff[8192]; + vsprintf(buff, mes, ap); + va_end(ap); + std::string tmp = buff; + int width = 0; + int height = 2; + size_t pos = tmp.find_first_of('\n'); + while (pos != std::string::npos) { + height++; + if (pos > width) + width = pos; + tmp = tmp.substr(pos + 1); + pos = tmp.find_first_of('\n'); + } + if (width == 0 || tmp.length() > width) + width = tmp.length(); + width += 2; + if (height > 25) + height = 25; + WINDOW* w = newwin(height + 1, width, int((25 - height) / 2), + int((80 - width) / 2)); + wborder(w, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, + LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); + tmp = buff; + pos = tmp.find_first_of('\n'); + int line_num = 0; + while (pos != std::string::npos) { + std::string line = tmp.substr(0, pos); + line_num++; + mvwprintz(w, line_num, 1, c_white, line.c_str()); + tmp = tmp.substr(pos + 1); + pos = tmp.find_first_of('\n'); + } + line_num++; + mvwprintz(w, line_num, 1, c_white, tmp.c_str()); + + wrefresh(w); + delwin(w); + refresh(); +} + void full_screen_popup(const char* mes, ...) { va_list ap; diff --git a/output.h b/output.h index 580374f82e..9c3f517197 100644 --- a/output.h +++ b/output.h @@ -42,6 +42,7 @@ int menu_vec(const char *mes, std::vector options); int menu(const char *mes, ...); void popup_top(const char *mes, ...); // Displayed at the top of the screen void popup(const char *mes, ...); +void popup_nowait(const char *mes, ...); // Doesn't wait for spacebar void full_screen_popup(const char *mes, ...); nc_color hilite(nc_color c); diff --git a/player.cpp b/player.cpp index 8fb6a50a9d..9eaa174182 100644 --- a/player.cpp +++ b/player.cpp @@ -50,6 +50,7 @@ player::player() oxygen = 0; active_mission = -1; xp_pool = 0; + last_item = itype_id(itm_null); for (int i = 0; i < num_skill_types; i++) { sklevel[i] = 0; skexercise[i] = 0; @@ -140,6 +141,8 @@ void player::reset(game *g) per_cur = per_max; // We can dodge again! can_dodge = true; +// Didn't just pick something up + last_item = itype_id(itm_null); // Bionic buffs if (has_active_bionic(bio_hydraulics)) str_cur += 20; @@ -422,7 +425,7 @@ void player::load_info(game *g, std::string data) max_power_level >> hunger >> thirst >> fatigue >> stim >> pain >> pkill >> radiation >> cash >> recoil >> scent >> moves >> underwater >> can_dodge >> oxygen >> active_mission >> xp_pool >> - male; + male >> health; for (int i = 0; i < PF_MAX2; i++) dump >> my_traits[i]; @@ -492,7 +495,8 @@ std::string player::save_info() " " << stim << " " << pain << " " << pkill << " " << radiation << " " << cash << " " << recoil << " " << scent << " " << moves << " " << underwater << " " << can_dodge << " " << oxygen << " " << - active_mission << " " << xp_pool << " " << male << " "; + active_mission << " " << xp_pool << " " << male << " " << health << + " "; for (int i = 0; i < PF_MAX2; i++) dump << my_traits[i] << " "; @@ -1838,6 +1842,8 @@ void player::hit(game *g, body_part bphurt, int side, int dam, int cut) if (dam <= 0) return; + g->cancel_activity_query("You were hurt!"); + if (has_artifact_with(AEP_SNAKES) && dam >= 6) { int snakes = int(dam / 6); std::vector valid; @@ -1944,6 +1950,8 @@ void player::hurt(game *g, body_part bphurt, int side, int dam) if (dam <= 0) return; + g->cancel_activity_query("You were hurt!"); + if (has_trait(PF_PAINRESIST)) painadd = dam / 3; else @@ -2498,11 +2506,13 @@ void player::suffer(game *g) if (has_artifact_with(AEP_MUTAGENIC) && one_in(28800)) mutate(g); - if (is_wearing(itm_hazmat_suit)) - radiation += rng(0, g->m.radiation(posx, posy) / 12); - else + if (is_wearing(itm_hazmat_suit)) { + if (radiation < 100 * int(g->m.radiation(posx, posy) / 12)) + radiation += rng(0, g->m.radiation(posx, posy) / 12); + } else if (radiation < 100 * int(g->m.radiation(posx, posy) / 4)) radiation += rng(0, g->m.radiation(posx, posy) / 4); - if (rng(1, 1000) < radiation && int(g->turn) % 600 == 0) { + + if (rng(1, 1000) < radiation && (int(g->turn) % 150 == 0 || radiation > 2000)){ mutate(g); if (radiation > 2000) radiation = 2000; @@ -2720,6 +2730,7 @@ void player::sort_inv() void player::i_add(item it) { + last_item = itype_id(it.type->id); if (it.is_food() || it.is_ammo() || it.is_gun() || it.is_armor() || it.is_book() || it.is_tool() || it.is_weap() || it.is_food_container()) inv_sorted = false; @@ -3311,16 +3322,23 @@ bool player::eat(game *g, int index) debugmsg("player::eat(%s); comest is NULL!", eaten->tname(g).c_str()); return false; } - if (comest->tool != itm_null && !has_amount(comest->tool, 1)) { - if (!is_npc()) - g->add_msg("You need a %s to consume that!", - g->itypes[comest->tool]->name.c_str()); - return false; + if (comest->tool != itm_null) { + bool has = has_amount(comest->tool, 1); + if (g->itypes[comest->tool]->count_by_charges()) + has = has_charges(comest->tool, 1); + if (!has) { + if (!is_npc()) + g->add_msg("You need a %s to consume that!", + g->itypes[comest->tool]->name.c_str()); + return false; + } } bool overeating = (!has_trait(PF_GOURMAND) && hunger < 0 && comest->nutr >= 15); bool spoiled = eaten->rotten(g); + last_item = itype_id(eaten->type->id); + if (overeating && !is_npc() && !query_yn("You're full. Force yourself to eat?")) return false; @@ -3381,7 +3399,8 @@ bool player::eat(game *g, int index) } else if (g->u_see(posx, posy, linet)) g->add_msg("%s eats a %s.", name.c_str(), eaten->tname(g).c_str()); - if (g->itypes[comest->tool]->is_tool()) + if (g->itypes[comest->tool]->is_tool() && + g->itypes[comest->tool]->count_by_charges()) use_charges(comest->tool, 1); // Tools like lighters get used if (comest->stim > 0) { if (comest->stim < 10 && stim < comest->stim) { @@ -3505,6 +3524,7 @@ bool player::wield(game *g, int index) g->add_artifact_messages(art->effects_wielded); } moves -= 30; + last_item = itype_id(weapon.type->id); return true; } else if (volume_carried() + weapon.volume() - inv[index].volume() < volume_capacity()) { @@ -3517,6 +3537,7 @@ bool player::wield(game *g, int index) it_artifact_tool *art = dynamic_cast(weapon.type); g->add_artifact_messages(art->effects_wielded); } + last_item = itype_id(weapon.type->id); return true; } else if (query_yn("No space in inventory for your %s. Drop it?", weapon.tname(g).c_str())) { @@ -3529,6 +3550,7 @@ bool player::wield(game *g, int index) it_artifact_tool *art = dynamic_cast(weapon.type); g->add_artifact_messages(art->effects_wielded); } + last_item = itype_id(weapon.type->id); return true; } @@ -3629,6 +3651,7 @@ bool player::wear(game *g, char let) g->add_artifact_messages(art->effects_worn); } moves -= 350; // TODO: Make this variable? + last_item = itype_id(to_wear->type->id); worn.push_back(*to_wear); if (index == -2) weapon = ret_null; @@ -3683,6 +3706,8 @@ void player::use(game *g, char let) return; } + last_item = itype_id(used->type->id); + if (used->is_tool()) { it_tool *tool = dynamic_cast(used->type); @@ -3771,6 +3796,13 @@ press 'U' while wielding the unloaded gun.", gun->tname(g).c_str()); inv.add_item(copy); return; } + if ((mod->id == itm_clip || mod->id == itm_clip2) && gun->clip_size() <= 2) { + g->add_msg("You can not extend the ammo capacity of your %s.", + gun->tname(g).c_str()); + if (replace_item) + inv.add_item(copy); + return; + } for (int i = 0; i < gun->contents.size(); i++) { if (gun->contents[i].type->id == used->type->id) { g->add_msg("Your %s already has a %s.", gun->tname(g).c_str(), @@ -3793,13 +3825,6 @@ press 'U' while wielding the unloaded gun.", gun->tname(g).c_str()); if (replace_item) inv.add_item(copy); return; - } else if ((mod->id == itm_clip || mod->id == itm_clip2) && - gun->clip_size() <= 2) { - g->add_msg("You can not extend the ammo capacity of your %s.", - gun->tname(g).c_str()); - if (replace_item) - inv.add_item(copy); - return; } } g->add_msg("You attach the %s to your %s.", used->tname(g).c_str(), diff --git a/player.h b/player.h index 1e7b538a5d..c543614e28 100644 --- a/player.h +++ b/player.h @@ -246,6 +246,7 @@ class player { bool inv_sorted; //std::vector inv; inventory inv; + itype_id last_item; std::vector worn; item weapon; item ret_null; // Null item, sometimes returns by weapon() etc diff --git a/pldata.h b/pldata.h index 983a9ba6ea..41a5277bf7 100644 --- a/pldata.h +++ b/pldata.h @@ -607,8 +607,8 @@ You have a short, stubby tail, like a rabbit's. It serves no purpose."}, {"Tail Fin", 1, 4, 2, "\ You have a fin-like tail. It allows you to swim more quickly."}, {"Long Tail", 2, 6, 2, "\ -You have a long, graceful tail. It improves your balance, making your\n\ -ability to dodge higher."}, +You have a long, graceful tail, like that of a big cat. It improves your\n\ +balance, making your ability to dodge higher."}, {"Fluffy Tail", 2, 7, 0, "\ You have a long, fluffy-furred tail. It greatly improves your balance,\n\ making your ability to dodge much higher."}, diff --git a/ranged.cpp b/ranged.cpp index 196b6185d1..cf4bfd8b77 100644 --- a/ranged.cpp +++ b/ranged.cpp @@ -104,6 +104,10 @@ void game::fire(player &p, int tarx, int tary, std::vector &trajectory, int target_picked = rng(0, new_targets.size() - 1); tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; + if (m.sees(p.posx, p.posy, tarx, tary, 0, tart)) + trajectory = line_to(p.posx, p.posy, tarx, tary, tart); + else + trajectory = line_to(p.posx, p.posy, tarx, tary, 0); } } int trange = calculate_range(p, tarx, tary); diff --git a/setvector.cpp b/setvector.cpp index a7082a7627..ab6f628bcb 100644 --- a/setvector.cpp +++ b/setvector.cpp @@ -108,17 +108,12 @@ void setvector(std::vector &vec, ... ) va_end(ap); } -/* -void setvector(std::vector &bec, ... ) +void setvector(std::vector &vec, ... ) { va_list ap; va_start(ap, vec); - ter_id tmpter; - int tmptime; - while (tmp = (ter_id)va_arg(ap, int)) { - tmptime = (int)va_arg(ap, int); - vec.push_back(construction_stage(tmpter, tmptime)); - } + moncat_id tmp; + while (tmp = (moncat_id)va_arg(ap, int)) + vec.push_back(tmp); va_end(ap); } -*/ diff --git a/setvector.h b/setvector.h index f288860d0a..fe277cfc6b 100644 --- a/setvector.h +++ b/setvector.h @@ -15,6 +15,6 @@ void setvector(std::vector &vec, ... ); void setvector(std::vector &vec, ... ); void setvector(std::vector &vec, ... ); void setvector(std::vector &vec, ... ); +void setvector(std::vector &vec, ... ); template void setvec(std::vector &vec, ... ); -//void setvector(std::vector &vec, ... ); #endif diff --git a/trapdef.cpp b/trapdef.cpp index 22a645ac6a..68a89c0ee9 100644 --- a/trapdef.cpp +++ b/trapdef.cpp @@ -26,11 +26,11 @@ traps.push_back(new trap(id, sym, color, name, visibility, avoidance,\ itm_beartrap, NULL); // Name Symbol Color Vis Avd Diff - TRAP("rabbit snare", '\\', c_brown, 5, 10, 6, + TRAP("rabbit snare", '\\', c_brown, 5, 10, 2, &trapfunc::snare, &trapfuncm::snare, itm_stick, itm_string_36, NULL); - TRAP("spiked board", '_', c_ltgray, 1, 6, 2, + TRAP("spiked board", '_', c_ltgray, 1, 6, 0, &trapfunc::board, &trapfuncm::board, itm_board_trap, NULL); @@ -74,7 +74,7 @@ traps.push_back(new trap(id, sym, color, name, visibility, avoidance,\ itm_null, NULL); // Name Symbol Color Vis Avd Diff - TRAP("dissector", '7', c_cyan, 2, 20, 20, + TRAP("dissector", '7', c_cyan, 2, 20, 99, &trapfunc::dissector, &trapfuncm::dissector, itm_null, NULL); diff --git a/tutorial.cpp b/tutorial.cpp new file mode 100644 index 0000000000..4e49878f09 --- /dev/null +++ b/tutorial.cpp @@ -0,0 +1,222 @@ +#include "game.h" +#include "output.h" +#include "action.h" +#include "tutorial.h" + +void tutorial_game::init(game *g) +{ + g->turn = HOURS(12); // Start at noon + for (int i = 0; i < NUM_LESSONS; i++) + tutorials_seen[i] = false; +// Set the scent map to 0 + for (int i = 0; i < SEEX * MAPSIZE; i++) { + for (int j = 0; j < SEEX * MAPSIZE; j++) + g->scent(i, j) = 0; + } + g->temperature = 65; +// We use a Z-factor of 10 so that we don't plop down tutorial rooms in the +// middle of the "real" game world + g->u.normalize(g); + g->u.str_cur = g->u.str_max; + g->u.per_cur = g->u.per_max; + g->u.int_cur = g->u.int_max; + g->u.dex_cur = g->u.dex_max; + g->u.name = "John Smith"; + g->levx = 100; + g->levy = 100; + g->cur_om = overmap(g, 0, 0, TUTORIAL_Z - 1); + g->cur_om.make_tutorial(); + g->cur_om.save(g->u.name, 0, 0, TUTORIAL_Z - 1); + g->cur_om = overmap(g, 0, 0, TUTORIAL_Z); + g->cur_om.make_tutorial(); + g->u.toggle_trait(PF_QUICK); + g->u.inv.push_back(item(g->itypes[itm_lighter], 0, 'e')); + g->u.sklevel[sk_gun] = 5; + g->u.sklevel[sk_melee] = 5; +// Init the starting map at g location. + for (int i = 0; i <= MAPSIZE; i += 2) { + for (int j = 0; j <= MAPSIZE; j += 2) { + tinymap tm(&g->itypes, &g->mapitems, &g->traps); + tm.generate(g, &(g->cur_om), g->levx + i - 1, g->levy + j - 1, int(g->turn)); + } + } +// Start with the overmap revealed + for (int x = 0; x < OMAPX; x++) { + for (int y = 0; y < OMAPY; y++) + g->cur_om.seen(x, y) = true; + } + g->m.load(g, g->levx, g->levy); + g->levz = 0; + g->u.posx = SEEX + 2; + g->u.posy = SEEY + 4; +} + +void tutorial_game::per_turn(game *g) +{ + if (g->turn == HOURS(12)) { + add_message(g, LESSON_INTRO); + add_message(g, LESSON_INTRO); + } else if (g->turn == HOURS(12) + 3) + add_message(g, LESSON_INTRO); + + if (g->light_level() == 1) { + if (g->u.has_amount(itm_flashlight, 1)) + add_message(g, LESSON_DARK); + else + add_message(g, LESSON_DARK_NO_FLASH); + } + + if (g->u.pain > 0) + add_message(g, LESSON_PAIN); + + if (g->u.recoil >= 5) + add_message(g, LESSON_RECOIL); + + if (!tutorials_seen[LESSON_BUTCHER]) { + for (int i = 0; i < g->m.i_at(g->u.posx, g->u.posy).size(); i++) { + if (g->m.i_at(g->u.posx, g->u.posy)[i].type->id == itm_corpse) { + add_message(g, LESSON_BUTCHER); + i = g->m.i_at(g->u.posx, g->u.posy).size(); + } + } + } + + bool showed_message = false; + for (int x = g->u.posx - 1; x <= g->u.posx + 1 && !showed_message; x++) { + for (int y = g->u.posy - 1; y <= g->u.posy + 1 && !showed_message; y++) { + if (g->m.ter(x, y) == t_door_o) { + add_message(g, LESSON_OPEN); + showed_message = true; + } else if (g->m.ter(x, y) == t_door_c) { + add_message(g, LESSON_CLOSE); + showed_message = true; + } else if (g->m.ter(x, y) == t_window) { + add_message(g, LESSON_SMASH); + showed_message = true; + } else if (g->m.ter(x, y) == t_rack && !g->m.i_at(x, y).empty()) { + add_message(g, LESSON_EXAMINE); + showed_message = true; + } else if (g->m.ter(x, y) == t_stairs_down) { + add_message(g, LESSON_STAIRS); + showed_message = true; + } else if (g->m.ter(x, y) == t_water_sh) { + add_message(g, LESSON_PICKUP_WATER); + showed_message = true; + } + } + } + + if (!g->m.i_at(g->u.posx, g->u.posy).empty()) + add_message(g, LESSON_PICKUP); +} + +void tutorial_game::pre_action(game *g, action_id &act) +{ +} + +void tutorial_game::post_action(game *g, action_id act) +{ + switch (act) { + case ACTION_RELOAD: + if (g->u.weapon.is_gun() && !tutorials_seen[LESSON_GUN_FIRE]) { + monster tmp(g->mtypes[mon_zombie], g->u.posx, g->u.posy - 6); + g->z.push_back(tmp); + tmp.spawn(g->u.posx + 2, g->u.posy - 5); + g->z.push_back(tmp); + tmp.spawn(g->u.posx - 2, g->u.posy - 5); + g->z.push_back(tmp); + add_message(g, LESSON_GUN_FIRE); + } + break; + + case ACTION_OPEN: + add_message(g, LESSON_CLOSE); + break; + + case ACTION_CLOSE: + add_message(g, LESSON_SMASH); + break; + + case ACTION_USE: + if (g->u.has_amount(itm_grenade_act, 1)) + add_message(g, LESSON_ACT_GRENADE); + for (int x = g->u.posx - 1; x <= g->u.posx + 1; x++) { + for (int y = g->u.posy - 1; y <= g->u.posy + 1; y++) { + if (g->m.tr_at(x, y) == tr_bubblewrap) + add_message(g, LESSON_ACT_BUBBLEWRAP); + } + } + break; + + case ACTION_EAT: + if (g->u.last_item == itm_codeine) + add_message(g, LESSON_TOOK_PAINKILLER); + else if (g->u.last_item == itm_cig) + add_message(g, LESSON_TOOK_CIG); + else if (g->u.last_item == itm_water) + add_message(g, LESSON_DRANK_WATER); + break; + + case ACTION_WEAR: { + itype *it = g->itypes[ g->u.last_item]; + if (it->is_armor()) { + it_armor *armor = dynamic_cast(it); + if (armor->dmg_resist >= 2 || armor->cut_resist >= 4) + add_message(g, LESSON_WORE_ARMOR); + if (armor->storage >= 20) + add_message(g, LESSON_WORE_STORAGE); + if (armor->env_resist >= 2) + add_message(g, LESSON_WORE_MASK); + } + } break; + + case ACTION_WIELD: + if (g->u.weapon.is_gun()) + add_message(g, LESSON_GUN_LOAD); + break; + + case ACTION_EXAMINE: + add_message(g, LESSON_INTERACT); +// Fall through to... + case ACTION_PICKUP: { + itype *it = g->itypes[ g->u.last_item ]; + if (it->is_armor()) + add_message(g, LESSON_GOT_ARMOR); + else if (it->is_gun()) + add_message(g, LESSON_GOT_GUN); + else if (it->is_ammo()) + add_message(g, LESSON_GOT_AMMO); + else if (it->is_tool()) + add_message(g, LESSON_GOT_TOOL); + else if (it->is_food()) + add_message(g, LESSON_GOT_FOOD); + else if (it->melee_dam > 7 || it->melee_cut > 5) + add_message(g, LESSON_GOT_WEAPON); + + if (g->u.volume_carried() > g->u.volume_capacity() - 2) + add_message(g, LESSON_OVERLOADED); + } break; + + } +} + +void tutorial_game::add_message(game *g, tut_lesson lesson) +{ +// Cycle through intro lessons + if (lesson == LESSON_INTRO) { + while (lesson != NUM_LESSONS && tutorials_seen[lesson]) { + switch (lesson) { + case LESSON_INTRO: lesson = LESSON_MOVE; break; + case LESSON_MOVE: lesson = LESSON_LOOK; break; + case LESSON_LOOK: lesson = NUM_LESSONS; break; + } + } + if (lesson == NUM_LESSONS) + return; + } + if (tutorials_seen[lesson]) + return; + tutorials_seen[lesson] = true; + popup_top(tut_text[lesson].c_str()); + g->refresh_all(); +} diff --git a/tutorial.h b/tutorial.h index 064624d196..9c4300ed76 100644 --- a/tutorial.h +++ b/tutorial.h @@ -1,30 +1,5 @@ #include -enum tut_lesson { -LESSON_INTRO, -LESSON_MOVE, LESSON_LOOK, LESSON_OPEN, LESSON_CLOSE, LESSON_SMASH, - LESSON_WINDOW, LESSON_PICKUP, LESSON_EXAMINE, LESSON_INTERACT, - -LESSON_FULL_INV, LESSON_WIELD_NO_SPACE, LESSON_AUTOWIELD, LESSON_ITEM_INTO_INV, - LESSON_GOT_ARMOR, LESSON_GOT_WEAPON, LESSON_GOT_FOOD, LESSON_GOT_TOOL, - LESSON_GOT_GUN, LESSON_GOT_AMMO, LESSON_WORE_ARMOR, LESSON_WORE_STORAGE, - LESSON_WORE_MASK, - -LESSON_WEAPON_INFO, LESSON_HIT_MONSTER, LESSON_PAIN, LESSON_BUTCHER, - -LESSON_TOOK_PAINKILLER, LESSON_TOOK_CIG, LESSON_DRANK_WATER, - -LESSON_ACT_GRENADE, LESSON_ACT_BUBBLEWRAP, - -LESSON_OVERLOADED, - -LESSON_GUN_LOAD, LESSON_GUN_FIRE, LESSON_RECOIL, - -LESSON_STAIRS, LESSON_DARK_NO_FLASH, LESSON_DARK, LESSON_PICKUP_WATER, - -NUM_LESSONS -}; - const std::string tut_text[] = {"\ Welcome to the Cataclysm tutorial! As you play, pop-ups like this one will\n\ appear to guide you through the basic game actions. Pressing spacebar will\n\ From 0f9d6ff13909535df930a15ee3df91b1096023f4 Mon Sep 17 00:00:00 2001 From: Whales Date: Sun, 22 Apr 2012 23:23:35 -0400 Subject: [PATCH 05/51] Some bug fixes, minor missing features, new motd --- data/motd | 26 +++++++++++++------------- defense.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/data/motd b/data/motd index 4a31c999e0..38912691f8 100644 --- a/data/motd +++ b/data/motd @@ -1,17 +1,17 @@ # This message may be 16 lines long at maximum. # Max length of a line is 64 characters; the following line is for reference ################################################################ -Welcome to 2012, roguelike fans! -I was away on vacation for a few weeks, so excuse my absence and -the small-ish update. This update focuses on a new mutation -system. It now works on a tech tree of sorts; one mutation can -lead to another, some mutations cancel each other, and mutations -are grouped into types; tree-like or fish-like, for example. -Mutating inside a type increases your chances of mutating in -that type in the future. -There's also some rebalance tweaks here; most notably, the XP -pool is capped rather low, and shooting at fast-moving monsters -is harder. +In this version, there's a new special game mode: Defense. -Please visit the official Cataclysm forums: -http://whalesdev.com/forums/index.php +Defense Mode, and the Tutorial, can be found under Special... +<--- Here. + +In Defense Mode, you start in a chosen location, and have to +fight off increasingly difficult waves of enemies. There is a +generator nearby which you have to defend. +To aid you, there are regular caravans from which you can buy +supplies. Additionally, crafting performs much, much faster. + +It's relatively easy to add new game modes. Read the +code_doc/GAMEMODES file for instructions on the basic, and +check out defense.cpp for some examples. diff --git a/defense.cpp b/defense.cpp index 17e234a034..68a320d991 100644 --- a/defense.cpp +++ b/defense.cpp @@ -110,6 +110,36 @@ void defense_game::pre_action(game *g, action_id &act) g->add_msg("You cannot save in defense mode!"); act = ACTION_NULL; } + +// Big ugly block for movement + if ((act == ACTION_MOVE_N && g->u.posy == SEEX * int(MAPSIZE / 2) && + g->levy <= 93) || + (act == ACTION_MOVE_NE && ((g->u.posy == SEEY * int(MAPSIZE / 2) && + g->levy <= 93) || + (g->u.posx == SEEX * (1 + int(MAPSIZE / 2))-1 && + g->levx >= 98))) || + (act == ACTION_MOVE_E && g->u.posx == SEEX * (1 + int(MAPSIZE / 2)) - 1 && + g->levx >= 98) || + (act == ACTION_MOVE_SE && ((g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && + g->levy >= 98) || + (g->u.posx == SEEX * (1 + int(MAPSIZE / 2))-1 && + g->levx >= 98))) || + (act == ACTION_MOVE_S && g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && + g->levy >= 98) || + (act == ACTION_MOVE_SW && ((g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && + g->levy >= 98) || + (g->u.posx == SEEX * int(MAPSIZE / 2) && + g->levx <= 93))) || + (act == ACTION_MOVE_W && g->u.posx == SEEX * int(MAPSIZE / 2) && + g->levx <= 93) || + (act == ACTION_MOVE_NW && ((g->u.posy == SEEY * int(MAPSIZE / 2) && + g->levy <= 93) || + (g->u.posx == SEEX * int(MAPSIZE / 2) && + g->levx <= 93)))) { + g->add_msg("You cannot leave the %s behind!", + defense_location_name(location).c_str()); + act = ACTION_NULL; + } } void defense_game::post_action(game *g, action_id act) From 84e7b70bfb74db305513e17186dd9c281de46aaf Mon Sep 17 00:00:00 2001 From: Whales Date: Thu, 26 Apr 2012 20:39:57 -0400 Subject: [PATCH 06/51] Bug fixes left and right --- action.cpp | 14 ++++ action.h | 1 + bionics.cpp | 6 +- code_doc/TODO | 1 - construction.cpp | 7 +- data/keymap.txt | 6 +- defense.cpp | 23 +++++- field.cpp | 1 + game.cpp | 199 +++++++++++++++++++++++++++-------------------- game.h | 1 + gamemode.h | 6 +- help.cpp | 4 +- itypedef.cpp | 20 ++--- iuse.cpp | 20 ++--- keypress.cpp | 111 +++++++++++++++++++++++++- keypress.h | 7 ++ map.cpp | 14 ++-- mapgen.cpp | 25 +++++- mapitemsdef.cpp | 2 +- melee.cpp | 2 +- mission.h | 2 +- monattack.cpp | 3 +- mtypedef.cpp | 2 +- newcharacter.cpp | 11 ++- omdata.h | 8 ++ overmap.cpp | 2 +- player.cpp | 5 +- pldata.h | 12 +-- ranged.cpp | 2 +- tutorial.cpp | 4 +- 30 files changed, 371 insertions(+), 150 deletions(-) diff --git a/action.cpp b/action.cpp index c1031f93c4..4cadb8a240 100644 --- a/action.cpp +++ b/action.cpp @@ -1,4 +1,5 @@ #include "game.h" +#include "keypress.h" #include action_id look_up_action(std::string ident); @@ -7,6 +8,17 @@ void game::load_keyboard_settings() { std::ifstream fin; fin.open("data/keymap.txt"); + if (!fin) { // It doesn't exist + std::ofstream fout; + fout.open("data/keymap.txt"); + fout << default_keymap_txt(); + fout.close(); + fin.open("data/keymap.txt"); + } + if (!fin) { // Still can't open it--probably bad permissions + debugmsg("Can't open data/keymap.txt. This may be a permissions issue."); + return; + } while (!fin.eof()) { std::string id; fin >> id; @@ -79,6 +91,8 @@ action_id look_up_action(std::string ident) return ACTION_LOOK; if (ident == "inventory") return ACTION_INVENTORY; + if (ident == "organize") + return ACTION_ORGANIZE; if (ident == "apply") return ACTION_USE; if (ident == "wear") diff --git a/action.h b/action.h index 66b4d1245e..a8cd2b4124 100644 --- a/action.h +++ b/action.h @@ -26,6 +26,7 @@ ACTION_CHAT, ACTION_LOOK, // Inventory Interaction (including quasi-inventories like bionics) ACTION_INVENTORY, +ACTION_ORGANIZE, ACTION_USE, ACTION_WEAR, ACTION_TAKE_OFF, diff --git a/bionics.cpp b/bionics.cpp index 0abe83d74c..28aaef1af2 100644 --- a/bionics.cpp +++ b/bionics.cpp @@ -200,7 +200,7 @@ void player::activate_bionic(int b, game *g) case bio_lighter: g->draw(); mvprintw(0, 0, "Torch in which direction?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); power_level += bionics[bio_lighter].power_cost; @@ -252,7 +252,7 @@ void player::activate_bionic(int b, game *g) case bio_emp: g->draw(); mvprintw(0, 0, "Fire EMP in which direction?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); power_level += bionics[bio_emp].power_cost; @@ -337,7 +337,7 @@ void player::activate_bionic(int b, game *g) case bio_lockpick: g->draw(); mvprintw(0, 0, "Unlock in which direction?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); power_level += bionics[bio_lockpick].power_cost; diff --git a/code_doc/TODO b/code_doc/TODO index 8aebfab094..174b99bbec 100644 --- a/code_doc/TODO +++ b/code_doc/TODO @@ -98,7 +98,6 @@ GENERAL: Pushable furniture; push dressers, fridges, etc. in front of doors Configurable options: - Keybindings Colors (e.g. highlight color, currently blue) Also ncurses options, e.g. A_BLINK support--not all terminals can display bright background colors, so black-on-dark-gray diff --git a/construction.cpp b/construction.cpp index 8ed4fa502c..1e06313c98 100644 --- a/construction.cpp +++ b/construction.cpp @@ -155,6 +155,11 @@ void game::construction_menu() inventory total_inv; total_inv.form_from_map(this, point(u.posx, u.posy), PICKUP_RANGE); total_inv.add_stack(u.inv_dump()); + if (u.has_bionic(bio_tools)) { + item tools(itypes[itm_toolset], turn); + tools.charges = u.power_level; + total_inv += tools; + } do { // Determine where in the master list to start printing @@ -392,7 +397,7 @@ void game::place_construction(constructable *con) } mvprintz(0, 0, c_red, "Pick a direction in which to construct:"); int dirx, diry; - get_direction(dirx, diry, input()); + get_direction(this, dirx, diry, input()); if (dirx == -2) { add_msg("Invalid direction."); return; diff --git a/data/keymap.txt b/data/keymap.txt index 9a5e3169fc..11356de10f 100644 --- a/data/keymap.txt +++ b/data/keymap.txt @@ -1,7 +1,7 @@ # This is the keymapping for Cataclysm. # You can start a line with # to make it a comment--it will be ignored. # Blank lines are ignored too. -# Extra whitespace, including tabs, is ignored, so format things how you like. +# Extra whitespace, including tab, is ignored, so format things how you like. # The format for each line is an action identifier, followed by several # keys. Any action may have an unlimited number of keys bound to it. @@ -24,7 +24,7 @@ # For now, you must use vikeys, the numpad, or arrow keys for those actions. # This is planned to change in the future. -# Finally, there is no support for special keys, like tab, spacebar, Home, and +# Finally, there is no support for special keys, like spacebar, Home, and # so on. This is not a planned feature, but if it's important to you, please # let me know. @@ -53,6 +53,7 @@ look ; x # INVENTORY & QUASI-INVENTORY INTERACTION inventory i +organize = apply a wear W take_off T @@ -84,6 +85,7 @@ player_data @ map m : factions # morale % +help ? # DEBUG FUNCTIONS debug Z diff --git a/defense.cpp b/defense.cpp index 68a320d991..8e3076715a 100644 --- a/defense.cpp +++ b/defense.cpp @@ -50,11 +50,12 @@ defense_game::defense_game() init_to_style(DEFENSE_EASY); } -void defense_game::init(game *g) +bool defense_game::init(game *g) { g->turn = HOURS(12); // Start at noon g->temperature = 65; - g->u.create(g, PLTYPE_CUSTOM); + if (!g->u.create(g, PLTYPE_CUSTOM)) + return false; g->u.str_cur = g->u.str_max; g->u.per_cur = g->u.per_max; g->u.int_cur = g->u.int_max; @@ -80,6 +81,7 @@ void defense_game::init(game *g) popup_nowait("Please wait as the map generates [ 0%]"); init_map(g); caravan(g); + return true; } void defense_game::per_turn(game *g) @@ -239,7 +241,7 @@ void defense_game::init_map(game *g) for (int j = 0; j <= MAPSIZE * 2; j += 2) { int mx = g->levx - MAPSIZE + i, my = g->levy - MAPSIZE + j; int percent = 100 * ((j / 2 + MAPSIZE * (i / 2))) / - ((MAPSIZE + 1) * (MAPSIZE + 1)); + ((MAPSIZE) * (MAPSIZE + 1)); if (percent >= old_percent + 1) { popup_nowait("Please wait as the map generates [%s%d%]", (percent < 10 ? " " : ""), percent); @@ -260,6 +262,18 @@ void defense_game::init_map(game *g) g->update_map(g->u.posx, g->u.posy); monster generator(g->mtypes[mon_generator], g->u.posx + 1, g->u.posy + 1); +// Find a valid spot to spawn the generator + std::vector valid; + for (int x = g->u.posx - 1; x <= g->u.posx + 1; x++) { + for (int y = g->u.posy - 1; y <= g->u.posy + 1; y++) { + if (generator.can_move_to(g->m, x, y) && g->is_empty(x, y)) + valid.push_back( point(x, y) ); + } + } + if (!valid.empty()) { + point p = valid[rng(0, valid.size() - 1)]; + generator.spawn(p.x, p.y); + } generator.friendly = -1; g->z.push_back(generator); } @@ -1266,6 +1280,9 @@ void defense_game::spawn_wave_monster(game *g, mtype *type) tmp.wandx = g->u.posx; tmp.wandy = g->u.posy; tmp.wandf = 150; +// We wanna kill! + tmp.anger = 100; + tmp.morale = 100; g->z.push_back(tmp); } diff --git a/field.cpp b/field.cpp index 2a9e9d66f1..3fec764b99 100644 --- a/field.cpp +++ b/field.cpp @@ -672,6 +672,7 @@ void map::mon_in_field(int x, int y, game *g, monster *z) z->speed *= .8; remove_field(x, y); } + break; case fd_acid: if (!z->has_flag(MF_DIGS) && !z->has_flag(MF_FLIES) && diff --git a/game.cpp b/game.cpp index fcb721bc23..9993868e6e 100644 --- a/game.cpp +++ b/game.cpp @@ -343,8 +343,15 @@ fivedozenwhales@gmail.com."); } if (ch == 'l' || ch == '\n' || ch == '>') { if (sel2 >= 1 && sel2 < NUM_SPECIAL_GAMES) { + delete gamemode; gamemode = get_special_game( special_game_id(sel2) ); - gamemode->init(this); + if (!gamemode->init(this)) { + delete gamemode; + gamemode = new special_game; + u = player(); + delwin(w_open); + return (opening_screen()); + } start = true; ch = 0; } @@ -1166,6 +1173,10 @@ void game::get_input() refresh_all(); } break; + case ACTION_ORGANIZE: + reassign_item(); + break; + case ACTION_USE: use_item(); break; @@ -1608,6 +1619,16 @@ void game::advance_nextinv() nextinv++; } +void game::decrease_nextinv() +{ + if (nextinv == 'a') + nextinv = 'Z'; + else if (nextinv == 'A') + nextinv = 'z'; + else + nextinv++; +} + void game::add_msg(const char* msg, ...) { char buff[1024]; @@ -2717,7 +2738,7 @@ void game::monmove() i--; else { if (u.has_active_bionic(bio_alarm) && u.power_level >= 1 && - abs(z[i].posx - u.posx) <= 5 && abs(z[i].posy - u.posy) <= 5) { + rl_dist(u.posx, u.posy, z[i].posx, z[i].posy) <= 5) { u.power_level--; add_msg("Your motion alarm goes off!"); u.activity.type = ACT_NULL; @@ -3311,13 +3332,13 @@ void game::kill_mon(int index) debugmsg("Tried to kill monster %d! (%d in play)", index, z.size()); return; } - if (z[index].dead) - return; - z[index].dead = true; - kills[z[index].type->id]++; // Increment our kill counter - for (int i = 0; i < z[index].inv.size(); i++) - m.add_item(z[index].posx, z[index].posy, z[index].inv[i]); - z[index].die(this); + if (!z[index].dead) { + z[index].dead = true; + kills[z[index].type->id]++; // Increment our kill counter + for (int i = 0; i < z[index].inv.size(); i++) + m.add_item(z[index].posx, z[index].posy, z[index].inv[i]); + z[index].die(this); + } z.erase(z.begin()+index); if (last_target == index) last_target = -1; @@ -3331,69 +3352,69 @@ void game::explode_mon(int index) debugmsg("Tried to explode monster %d! (%d in play)", index, z.size()); return; } - if (z[index].dead) - return; - z[index].dead = true; - kills[z[index].type->id]++; // Increment our kill counter + if (!z[index].dead) { + z[index].dead = true; + kills[z[index].type->id]++; // Increment our kill counter // Send body parts and blood all over! - mtype* corpse = z[index].type; - if (corpse->mat == FLESH || corpse->mat == VEGGY) { // No chunks otherwise - int num_chunks; - switch (corpse->size) { - case MS_TINY: num_chunks = 1; break; - case MS_SMALL: num_chunks = 2; break; - case MS_MEDIUM: num_chunks = 4; break; - case MS_LARGE: num_chunks = 8; break; - case MS_HUGE: num_chunks = 16; break; - } - itype* meat; - if (corpse->has_flag(MF_POISON)) { - if (corpse->mat == FLESH) - meat = itypes[itm_meat_tainted]; - else - meat = itypes[itm_veggy_tainted]; - } else { - if (corpse->mat == FLESH) - meat = itypes[itm_meat]; - else - meat = itypes[itm_veggy]; - } - - int posx = z[index].posx, posy = z[index].posy; - for (int i = 0; i < num_chunks; i++) { - int tarx = posx + rng(-3, 3), tary = posy + rng(-3, 3); - std::vector traj = line_to(posx, posy, tarx, tary, 0); + mtype* corpse = z[index].type; + if (corpse->mat == FLESH || corpse->mat == VEGGY) { // No chunks otherwise + int num_chunks; + switch (corpse->size) { + case MS_TINY: num_chunks = 1; break; + case MS_SMALL: num_chunks = 2; break; + case MS_MEDIUM: num_chunks = 4; break; + case MS_LARGE: num_chunks = 8; break; + case MS_HUGE: num_chunks = 16; break; + } + itype* meat; + if (corpse->has_flag(MF_POISON)) { + if (corpse->mat == FLESH) + meat = itypes[itm_meat_tainted]; + else + meat = itypes[itm_veggy_tainted]; + } else { + if (corpse->mat == FLESH) + meat = itypes[itm_meat]; + else + meat = itypes[itm_veggy]; + } - bool done = false; - for (int j = 0; j < traj.size() && !done; j++) { - tarx = traj[j].x; - tary = traj[j].y; + int posx = z[index].posx, posy = z[index].posy; + for (int i = 0; i < num_chunks; i++) { + int tarx = posx + rng(-3, 3), tary = posy + rng(-3, 3); + std::vector traj = line_to(posx, posy, tarx, tary, 0); + + bool done = false; + for (int j = 0; j < traj.size() && !done; j++) { + tarx = traj[j].x; + tary = traj[j].y; // Choose a blood type and place it - field_id blood_type = fd_blood; - if (corpse->dies == &mdeath::boomer) - blood_type = fd_bile; - else if (corpse->dies == &mdeath::acid) - blood_type = fd_acid; - if (m.field_at(tarx, tary).type == blood_type && - m.field_at(tarx, tary).density < 3) - m.field_at(tarx, tary).density++; - else - m.add_field(this, tarx, tary, blood_type, 1); - - if (m.move_cost(tarx, tary) == 0) { - std::string tmp = ""; - if (m.bash(tarx, tary, 3, tmp)) - sound(tarx, tary, 18, tmp); - else { - if (j > 0) { - tarx = traj[j - 1].x; - tary = traj[j - 1].y; + field_id blood_type = fd_blood; + if (corpse->dies == &mdeath::boomer) + blood_type = fd_bile; + else if (corpse->dies == &mdeath::acid) + blood_type = fd_acid; + if (m.field_at(tarx, tary).type == blood_type && + m.field_at(tarx, tary).density < 3) + m.field_at(tarx, tary).density++; + else + m.add_field(this, tarx, tary, blood_type, 1); + + if (m.move_cost(tarx, tary) == 0) { + std::string tmp = ""; + if (m.bash(tarx, tary, 3, tmp)) + sound(tarx, tary, 18, tmp); + else { + if (j > 0) { + tarx = traj[j - 1].x; + tary = traj[j - 1].y; + } + done = true; } - done = true; } } + m.add_item(tarx, tary, meat, turn); } - m.add_item(tarx, tary, meat, turn); } } @@ -3413,7 +3434,7 @@ void game::open() int openx, openy; char ch = input(); last_action += ch; - get_direction(openx, openy, ch); + get_direction(this, openx, openy, ch); if (openx != -2 && openy != -2) if (m.ter(u.posx, u.posy) == t_floor) didit = m.open_door(u.posx + openx, u.posy + openy, true); @@ -3446,7 +3467,7 @@ void game::close() int closex, closey; char ch = input(); last_action += ch; - get_direction(closex, closey, ch); + get_direction(this, closex, closey, ch); if (closex != -2 && closey != -2) { closex += u.posx; closey += u.posy; @@ -3479,7 +3500,7 @@ void game::smash() return; } int smashx, smashy; - get_direction(smashx, smashy, ch); + get_direction(this, smashx, smashy, ch); // TODO: Move this elsewhere. if (m.has_flag(alarmed, u.posx + smashx, u.posy + smashy) && !event_queued(EVENT_WANTED)) { @@ -3532,7 +3553,7 @@ void game::examine() last_action += ch; if (ch == KEY_ESCAPE || ch == 'e' || ch == 'q') return; - get_direction(examx, examy, ch); + get_direction(this, examx, examy, ch); if (examx == -2 || examy == -2) { add_msg("Invalid direction."); return; @@ -3811,7 +3832,7 @@ point game::look_around() if (!u_see(lx, ly, junk)) mvwputch(w_terrain, ly - u.posy + SEEY, lx - u.posx + SEEX, c_black, ' '); draw_ter(); - get_direction(mx, my, ch); + get_direction(this, mx, my, ch); if (mx != -2 && my != -2) { // Directional key pressed lx += mx; ly += my; @@ -3914,9 +3935,12 @@ void game::pickup(int posx, int posy, int min) add_msg("You can't pick up a liquid!"); return; } - while (iter < 52 && (newit.invlet == 0 || - (u.has_item(newit.invlet) && - !u.i_at(newit.invlet).stacks_with(newit))) ) { + if (newit.invlet == 0) { + newit.invlet = nextinv; + advance_nextinv(); + } + while (iter < 52 && u.has_item(newit.invlet) && + !u.i_at(newit.invlet).stacks_with(newit)) { newit.invlet = nextinv; iter++; advance_nextinv(); @@ -3926,7 +3950,7 @@ void game::pickup(int posx, int posy, int min) return; } else if (u.weight_carried() + newit.weight() > u.weight_capacity()) { add_msg("The %s is too heavy!", newit.tname(this).c_str()); - nextinv--; + decrease_nextinv(); } else if (u.volume_carried() + newit.volume() > u.volume_capacity()) { if (u.is_armed()) { if (u.weapon.type->id < num_items && // Not a bionic @@ -3939,7 +3963,7 @@ void game::pickup(int posx, int posy, int min) u.moves -= 100; add_msg("Wielding %c - %s", newit.invlet, newit.tname(this).c_str()); } else - nextinv--; + decrease_nextinv(); } else { u.i_add(newit); u.wield(this, u.inv.size() - 1); @@ -4103,7 +4127,7 @@ void game::pickup(int posx, int posy, int min) return; } else if (u.weight_carried() + here[i].weight() > u.weight_capacity()) { add_msg("The %s is too heavy!", here[i].tname(this).c_str()); - nextinv--; + decrease_nextinv(); } else if (u.volume_carried() + here[i].volume() > u.volume_capacity()) { if (u.is_armed()) { if (u.weapon.type->id < num_items && // Not a bionic @@ -4116,7 +4140,7 @@ void game::pickup(int posx, int posy, int min) m.i_rem(posx, posy, curmit); curmit--; } else - nextinv--; + decrease_nextinv(); } else { u.i_add(here[i]); u.wield(this, u.inv.size() - 1); @@ -4288,7 +4312,7 @@ void game::drop_in_direction() refresh_all(); mvprintz(0, 0, c_red, "Choose a direction:"); int dirx, diry; - get_direction(dirx, diry, input()); + get_direction(this, dirx, diry, input()); if (dirx == -2) { add_msg("Invalid direction!"); return; @@ -5058,13 +5082,20 @@ void game::plmove(int x, int y) u.rem_disease(DI_ONFIRE); } if (displace) { // We displaced a friendly monster! -// TODO: This is hacky, but the turret is the only thing it's need for now - if (z[mondex].type->id == mon_turret) { - if (query_yn("Deactivate the turret?")) { - z.erase(z.begin() + mondex); - m.add_item(z[mondex].posx, z[mondex].posy, itypes[itm_bot_turret], turn); +// Immobile monsters can't be displaced. + if (z[mondex].has_flag(MF_IMMOBILE)) { +// ...except that turrets can be picked up. + if (z[mondex].type->id == mon_turret) { + if (query_yn("Deactivate the turret?")) { + z.erase(z.begin() + mondex); + u.moves -= 100; + m.add_item(z[mondex].posx, z[mondex].posy, itypes[itm_bot_turret], turn); + } + return; + } else { + add_msg("You can't displace your %s.", z[mondex].name().c_str()); + return; } - return; } z[mondex].move_to(this, u.posx, u.posy); add_msg("You displace the %s.", z[mondex].name().c_str()); diff --git a/game.h b/game.h index 6630a3ff6a..5c938bca63 100644 --- a/game.h +++ b/game.h @@ -69,6 +69,7 @@ class game void draw(); void draw_ter(); void advance_nextinv(); // Increment the next inventory letter + void decrease_nextinv(); // Decrement the next inventory letter void add_msg(const char* msg, ...); void add_event(event_type type, int on_turn, int faction_id = -1, int x = -1, int y = -1); diff --git a/gamemode.h b/gamemode.h index 9aa3fc2a1f..d3a3d30b16 100644 --- a/gamemode.h +++ b/gamemode.h @@ -24,7 +24,7 @@ struct special_game { virtual special_game_id id() { return SGAME_NULL; }; // init is run when the game begins - virtual void init(game *g) { }; + virtual bool init(game *g) { return true; }; // per_turn is run every turn--before any player actions virtual void per_turn(game *g) { }; // pre_action is run after a keypress, but before the game handles the action @@ -67,7 +67,7 @@ NUM_LESSONS struct tutorial_game : public special_game { virtual special_game_id id() { return SGAME_TUTORIAL; }; - virtual void init(game *g); + virtual bool init(game *g); virtual void per_turn(game *g); virtual void pre_action(game *g, action_id &act); virtual void post_action(game *g, action_id act); @@ -121,7 +121,7 @@ struct defense_game : public special_game defense_game(); virtual special_game_id id() { return SGAME_DEFENSE; }; - virtual void init(game *g); + virtual bool init(game *g); virtual void per_turn(game *g); virtual void pre_action(game *g, action_id &act); virtual void post_action(game *g, action_id act); diff --git a/help.cpp b/help.cpp index 200f431d07..ff53416d98 100644 --- a/help.cpp +++ b/help.cpp @@ -150,7 +150,7 @@ the more intense high of Adderall and methamphetamine."); erase(); mvprintz(0, 0, c_white, "\ Many drugs have a potential for addiction. Each time you consume such a drug\n\ -there is a chance that you will grow dependant on it. Consuming more of that\n\ +there is a chance that you will grow dependent on it. Consuming more of that\n\ drug will increase your dependance. Effects vary greatly between drugs, but\n\ all addictions have only one cure; going cold turkey. The process may last\n\ days, and will leave you very weak, so try to do it in a safe area.\n\ @@ -290,7 +290,7 @@ replaced by some or all of its constituent parts; however, if you fail, there\n\ is a chance that you will set off the trap, suffering the consequences.\n\ \n\ Many traps are fully or partially hidden. Your ability to detect traps is\n\ -entirely dependant upon your Perception. Should you stumble into a trap, you\n\ +entirely dependent upon your Perception. Should you stumble into a trap, you\n\ may have a chance to avoid it, depending on your Dodge skill."); getch(); diff --git a/itypedef.cpp b/itypedef.cpp index 13a9e84b71..0941426833 100644 --- a/itypedef.cpp +++ b/itypedef.cpp @@ -310,7 +310,7 @@ Tomato sauce, yum yum."); FOOD("pesto", 15, 20, c_ltgreen, VEGGY, itm_can_food, 2, 3, 0, 18, 0, 0, 1, 0, 1, 4, &iuse::none, ADD_NULL, "\ -Olive oil, basil, garlic, pine nuts. Simple and deliicous."); +Olive oil, basil, garlic, pine nuts. Simple and delicious."); // NAME RAR PRC COLOR MAT1 CONTAINER FOOD("beans", 40, 55, c_cyan, VEGGY, itm_can_food, @@ -709,7 +709,7 @@ it from the manhole is impossible without a crowbar."); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("rock", 40, 0, '*', c_ltgray, STONE, MNULL, // VOL WGT DAM CUT HIT FLAGS - 1, 3, 12, 0, -3, 0, "\ + 1, 3, 12, 0, -2, 0, "\ A rock the size of a baseball. Makes a decent melee weapon, and is also good\n\ for throwing at enemies."); @@ -872,7 +872,7 @@ its sap-producing organ intact."); MELEE("empty canister", 5, 20,'*', c_ltgray, STEEL, MNULL, 1, 1, 2, 0, -1, 0, "\ -An empty cansiter, which may have once held tear gas or other substances."); +An empty canister, which may have once held tear gas or other substances."); MELEE("gold bar", 10,3000,'/', c_yellow, STEEL, MNULL, 2, 60, 14, 0, -1, 0, "\ @@ -1196,7 +1196,7 @@ of protection from air-borne illness and dust."); ARMOR("bandana", 35, 28,C_MOUTH, COTTON, MNULL, 1, 0, -4, -1, 0, 0, 0, 1, 2, 0, mfb(bp_mouth), "\ A cotton bandana, worn over the mouth for warmth and minor protection from\n\ -dust and other contaminents."); +dust and other contaminants."); ARMOR("scarf", 45, 40,C_MOUTH, WOOL, MNULL, 2, 3, -5, -3, 1, 1, 0, 2, 3, 0, mfb(bp_mouth), "\ @@ -1205,12 +1205,12 @@ A long wool scarf, worn over the mouth for warmth."); ARMOR("filter mask", 30, 80,C_MOUTH, PLASTIC, MNULL, 3, 6, 1, 1, 2, 1, 1, 7, 2, 0, mfb(bp_mouth), "\ A mask that straps over your mouth and nose and filters air. Protects from\n\ -smoke, dust, and other contaminents quite well."); +smoke, dust, and other contaminants quite well."); ARMOR("gas mask", 10, 240,C_MOUTH, PLASTIC, MNULL, 6, 8, 0, -3, 4, 1, 2, 16, 4, 0, mfb(bp_mouth)|mfb(bp_eyes), "\ A full gas mask that covers the face and eyes. Provides excellent protection\n\ -from smoke, teargas, and other contaminents."); +from smoke, teargas, and other contaminants."); // Eyewear - Encumberment is its effect on your eyesight. // Environment is the defense to your eyes from noxious fumes etc. @@ -1453,7 +1453,7 @@ chance of remaining intact once fired.", AMMO("birdshot", 8, 500,AT_SHOT, c_red, PLASTIC, 2, 25, 18, 0, 5, 2, 18, 25, "\ -Weak shotgun ammuntion. Designed for hunting birds and other small game, its\n\ +Weak shotgun ammunition. Designed for hunting birds and other small game, its\n\ applications in combat are very limited.", 0); @@ -1685,7 +1685,7 @@ AMMO("fusion pack", 2, 800,AT_FUSION, c_ltgreen, PLASTIC, 1, 2, 12, 6, 20, 4, 0, 20, "\ In the middle of the 21st Century, military powers began to look towards\n\ energy based weapons. The result was the standard fusion pack, capable of\n\ -delivering bolts of superheaed gas at near light speed with no recoil.", +delivering bolts of superheated gas at near light speed with no recoil.", mfb(IF_AMMO_INCENDIARY)); AMMO("40mm concussive", 10,400,AT_40MM, c_ltred, STEEL, @@ -2709,7 +2709,7 @@ provides light during the night or while underground. Use it to turn it off."); TOOL("hotplate", 10, 250,';', c_green, IRON, PLASTIC, 5, 6, 8, 0, -1, 40, 20, 0, 0, AT_BATT, itm_null, &iuse::none,0,"\ -A small heating element. Indispensible for cooking and chemisty."); +A small heating element. Indispensable for cooking and chemistry."); // NAME RAR PRC SYM COLOR MAT1 MAT TOOL("soldering iron", 70, 200,',', c_ltblue, IRON, MNULL, @@ -2978,7 +2978,7 @@ TOOL("portal generator",2, 6600, ';', c_magenta, STEEL, PLASTIC, 2, 10, 6, 0, -1, 10, 10, 5, 0, AT_NULL, itm_null, &iuse::portal,0,"\ A rare and arcane device, covered in alien markings."); -TOOL("inactive manhack",1, 1200, ',', c_ltgreen, STEEL, PLASTIC, +TOOL("inactive manhack",1, 600, ',', c_ltgreen, STEEL, PLASTIC, 1, 3, 6, 6, -3, 0, 0, 0, 0, AT_NULL, itm_null, &iuse::manhack,0,"\ An inactive manhack. Manhacks are fist-sized robots which fly through the\n\ air. They are covered with whirring blades and attack by throwing themselves\n\ diff --git a/iuse.cpp b/iuse.cpp index 16a08d3bf2..22a6721f50 100644 --- a/iuse.cpp +++ b/iuse.cpp @@ -613,7 +613,7 @@ void iuse::dogfood(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Which direction?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); return; @@ -642,7 +642,7 @@ void iuse::lighter(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Light where?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); it->charges++; @@ -826,7 +826,7 @@ void iuse::extinguisher(game *g, player *p, item *it, bool t) g->draw(); mvprintz(0, 0, c_red, "Pick a direction to spray:"); int dirx, diry; - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction!"); it->charges++; @@ -875,7 +875,7 @@ void iuse::hammer(game *g, player *p, item *it, bool t) g->draw(); mvprintz(0, 0, c_red, "Pick a direction in which to pry:"); int dirx, diry; - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction!"); return; @@ -1091,7 +1091,7 @@ void iuse::crowbar(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Pry where?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); return; @@ -1173,7 +1173,7 @@ void iuse::dig(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Dig where?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); return; @@ -1219,7 +1219,7 @@ void iuse::jackhammer(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Drill in which direction?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); return; @@ -1241,7 +1241,7 @@ void iuse::set_trap(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Place where?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); return; @@ -1818,7 +1818,7 @@ void iuse::turret(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Place where?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); return; @@ -1869,7 +1869,7 @@ void iuse::tazer(game *g, player *p, item *it, bool t) int dirx, diry; g->draw(); mvprintw(0, 0, "Shock in which direction?"); - get_direction(dirx, diry, input()); + get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); it->charges += (dynamic_cast(it->type))->charges_per_use; diff --git a/keypress.cpp b/keypress.cpp index 22aa7ea33a..fa28921e05 100644 --- a/keypress.cpp +++ b/keypress.cpp @@ -1,10 +1,12 @@ #include "keypress.h" #include "action.h" +#include "game.h" long input() { long ch = getch(); switch (ch) { +/* Legacy case '7': return 'y'; case KEY_UP: case '8': return 'k'; @@ -18,6 +20,7 @@ long input() case KEY_DOWN: case '2': return 'j'; case '3': return 'n'; +*/ case 459: return '\n'; default: return ch; } @@ -68,10 +71,16 @@ void get_direction(int &x, int &y, char ch) } } -void get_direction(int &x, int &y, action_id act) +void get_direction(game *g, int &x, int &y, char ch) { x = 0; y = 0; + action_id act; + if (g->keymap.find(ch) == g->keymap.end()) + act = ACTION_NULL; + else + act = g->keymap[ch]; + switch (act) { case ACTION_MOVE_NW: x = -1; @@ -110,3 +119,103 @@ void get_direction(int &x, int &y, action_id act) y = -2; } } + +std::string default_keymap_txt() +{ + return "\ +# This is the keymapping for Cataclysm.\n\ +# You can start a line with # to make it a comment--it will be ignored.\n\ +# Blank lines are ignored too.\n\ +# Extra whitespace, including tab, is ignored, so format things how you like.\n\ +# If you wish to restore defaults, simply remove this file.\n\ +\n\ +# The format for each line is an action identifier, followed by several\n\ +# keys. Any action may have an unlimited number of keys bound to it.\n\ +# If you bind the same key to multiple actions, the second and subsequent\n\ +# bindings will be ignored--and you'll get a warning when the game starts.\n\ +# Keys are case-sensitive, of course; c and C are different.\n\ + \n\ +# WARNING: If you skip an action identifier, there will be no key bound to\n\ +# that action! You will be NOT be warned of this when the game starts.\n\ +# If you're going to mess with stuff, maybe you should back this file up?\n\ +\n\ +# It is okay to split commands across lines.\n\ +# pause . 5 is equivalent to:\n\ +# pause .\n\ +# pause 5\n\ +\n\ +# Note that movement keybindings ONLY apply to movement (for now).\n\ +# That is, binding w to move_n will let you use w to move north, but you\n\ +# cannot use w to smash north, examine to the north, etc.\n\ +# For now, you must use vikeys, the numpad, or arrow keys for those actions.\n\ +# This is planned to change in the future.\n\ +\n\ +# Finally, there is no support for special keys, like spacebar, Home, and\n\ +# so on. This is not a planned feature, but if it's important to you, please\n\ +# let me know.\n\ +\n\ +# MOVEMENT:\n\ +pause . 5\n\ +move_n k 8\n\ +move_ne u 9\n\ +move_e l 6\n\ +move_se n 3\n\ +move_s j 2\n\ +move_sw b 1\n\ +move_w h 4\n\ +move_nw y 7\n\ +move_down >\n\ +move_up <\n\ +\n\ +# ENVIRONMENT INTERACTION\n\ +open o\n\ +close c\n\ +smash s\n\ +examine e\n\ +pickup , g\n\ +butcher B\n\ +chat C\n\ +look ; x\n\ +\n\ +# INVENTORY & QUASI-INVENTORY INTERACTION\n\ +inventory i\n\ +organize =\n\ +apply a\n\ +wear W\n\ +take_off T\n\ +eat E\n\ +read R\n\ +wield w\n\ +reload r\n\ +unload U\n\ +throw t\n\ +fire f\n\ +fire_burst F\n\ +drop d\n\ +drop_adj D\n\ +bionics p\n\ +\n\ +# LONG TERM & SPECIAL ACTIONS\n\ +wait ^\n\ +craft &\n\ +construct *\n\ +sleep $\n\ +safemode !\n\ +autosafe \"\n\ +ignore_enemy '\n\ +save S\n\ +quit Q\n\ +\n\ +# INFO SCREENS\n\ +player_data @\n\ +map m :\n\ +factions #\n\ +morale %\n\ +help ?\n\ +\n\ +# DEBUG FUNCTIONS\n\ +debug Z\n\ +debug_scent -\n\ +debug_mon ~\n\ +"; +} diff --git a/keypress.h b/keypress.h index b4741845eb..bfa042fafd 100644 --- a/keypress.h +++ b/keypress.h @@ -6,10 +6,17 @@ #include #endif +#include + +class game; + // Simple text input--translates numpad to vikeys long input(); // If ch is vikey, x & y are set to corresponding direction; ch=='y'->x=-1,y=-1 void get_direction(int &x, int &y, char ch); +// Uses the keymap to figure out direction properly +void get_direction(game *g, int &x, int &y, char ch); +std::string default_keymap_txt(); #define CTRL(n) (n - 'A' + 1 < 1 ? n - 'a' + 1 : n - 'A' + 1) #define KEY_ESCAPE 27 diff --git a/map.cpp b/map.cpp index 8c32dbcd32..781f820b5b 100644 --- a/map.cpp +++ b/map.cpp @@ -496,6 +496,8 @@ void map::destroy(game *g, int x, int y, bool makesound) void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) { + if (dam < 0) + return; if (has_flag(alarmed, x, y) && !g->event_queued(EVENT_WANTED)) { g->sound(g->u.posx, g->u.posy, 30, "An alarm sounds!"); @@ -594,6 +596,10 @@ void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) if (flags & mfb(IF_AMMO_TRAIL) && !one_in(4)) add_field(g, x, y, fd_smoke, rng(1, 2)); +// Set damage to 0 if it's less + if (dam < 0) + dam = 0; + // Now, destroy items on that tile. if ((move_cost(x, y) == 2 && !hit_items) || !INBOUNDS(x, y)) @@ -922,9 +928,7 @@ void map::use_amount(point origin, int range, itype_id type, int quantity, for (int radius = 0; radius <= range && quantity > 0; radius++) { for (int x = origin.x - radius; x <= origin.x + radius; x++) { for (int y = origin.y - radius; y <= origin.y + radius; y++) { - if (rl_dist(origin.x, origin.y, x, y) < radius) - y++; // Skip over already-examined tiles - else { + if (rl_dist(origin.x, origin.y, x, y) >= radius) { for (int n = 0; n < i_at(x, y).size() && quantity > 0; n++) { item* curit = &(i_at(x, y)[n]); bool used_contents = false; @@ -956,9 +960,7 @@ void map::use_charges(point origin, int range, itype_id type, int quantity) for (int radius = 0; radius <= range && quantity > 0; radius++) { for (int x = origin.x - radius; x <= origin.x + radius; x++) { for (int y = origin.y - radius; y <= origin.y + radius; y++) { - if (rl_dist(origin.x, origin.y, x, y) < radius) - y++; // Skip over already-examined tiles - else { + if (rl_dist(origin.x, origin.y, x, y) >= radius) { for (int n = 0; n < i_at(x, y).size(); n++) { item* curit = &(i_at(x, y)[n]); // Check contents first diff --git a/mapgen.cpp b/mapgen.cpp index 1bd41663c0..0e12893790 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -4994,6 +4994,27 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, line(this, t_wall_h, 1, 13, 10, 13); line(this, t_wall_h, 13, 10, rw, 10); line(this, t_wall_h, 13, 13, rw, 13); +// Doors + if (one_in(2)) + ter(10, rng(tw + 1, 8)) = t_door_c; + else + ter(rng(2, 8), 10) = t_door_c; + + if (one_in(2)) + ter(13, rng(tw + 1, 8)) = t_door_c; + else + ter(rng(15, rw - 1), 10) = t_door_c; + + if (one_in(2)) + ter(10, rng(15, SEEY * 2 - 3)) = t_door_c; + else + ter(rng(2, 8), 13) = t_door_c; + + if (one_in(2)) + ter(13, rng(15, SEEY * 2 - 3)) = t_door_c; + else + ter(rng(15, rw - 1), 13) = t_door_c; + mansion_room(this, 1, tw, 9, 9); mansion_room(this, 14, tw, rw, 9); mansion_room(this, 1, 14, 9, SEEY * 2 - 2); @@ -5098,10 +5119,8 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, consecutive = 0; } } - break; - case ot_spider_pit_under: for (int i = 0; i < SEEX * 2; i++) { for (int j = 0; j < SEEY * 2; j++) { @@ -7330,7 +7349,7 @@ room_type pick_mansion_room(int x1, int y1, int x2, int y2) valid.push_back(room_mansion_game); if (shortest >= 10) valid.push_back(room_mansion_pool); - if (longest <= 6) + if (longest <= 6 || shortest <= 4) valid.push_back(room_mansion_bathroom); if (longest >= 8 && shortest <= 6) valid.push_back(room_mansion_gallery); diff --git a/mapitemsdef.cpp b/mapitemsdef.cpp index a4aab7a0cb..e2fb5e9834 100644 --- a/mapitemsdef.cpp +++ b/mapitemsdef.cpp @@ -182,7 +182,7 @@ void game::init_mapitems() itm_amplifier, itm_antenna, itm_battery, itm_soldering_iron, itm_screwdriver, itm_processor, itm_RAM, itm_mp3, itm_flashlight, itm_radio, itm_hotplate, itm_receiver, itm_transponder, itm_tazer, - itm_two_way_radio, itm_usb_drive, NULL); + itm_two_way_radio, itm_usb_drive, itm_manual_electronics, NULL); setvector( mapitems[mi_sports], diff --git a/melee.cpp b/melee.cpp index 943324446d..43b1927aa9 100644 --- a/melee.cpp +++ b/melee.cpp @@ -497,11 +497,11 @@ int player::hit_mon(game *g, monster *z) if (is_u) g->add_msg("Your %s gets stuck in the %s, pulling it out of your hands!", weapon.tname().c_str(), z->type->name.c_str()); - z->add_item(remove_weapon()); if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .7; else z->speed *= .85; + z->add_item(remove_weapon()); } else { if (dam >= z->hp) { cutting_penalty /= 2; diff --git a/mission.h b/mission.h index b0559177f2..b66dc5075d 100644 --- a/mission.h +++ b/mission.h @@ -60,7 +60,7 @@ struct mission_start { void infect_npc (game *, mission *); // DI_INFECTION, remove antibiotics void place_dog (game *, mission *); // Put a dog in a house! void place_zombie_mom (game *, mission *); // Put a zombie mom in a house! - void place_npc_software(game *, mission *); // Put NPC-type-dependant software + void place_npc_software(game *, mission *); // Put NPC-type-dependent software void reveal_hospital (game *, mission *); // Reveal the nearest hospital }; diff --git a/monattack.cpp b/monattack.cpp index 146a9df837..7572767865 100644 --- a/monattack.cpp +++ b/monattack.cpp @@ -1168,13 +1168,14 @@ void mattack::smg(game *g, monster *z) !g->sees_u(z->posx, z->posy, t)) return; z->sp_timeout = z->type->sp_freq; // Reset timer - z->moves = -150; // It takes a while if (!z->has_effect(ME_TARGETED)) { g->sound(z->posx, z->posy, 6, "beep-beep-beep!"); z->add_effect(ME_TARGETED, 8); + z->moves -= 100; return; } + z->moves = -150; // It takes a while if (g->u_see(z->posx, z->posy, j)) g->add_msg("The %s fires its smg!", z->name().c_str()); diff --git a/mtypedef.cpp b/mtypedef.cpp index 507d53bfe9..df3b7a231d 100644 --- a/mtypedef.cpp +++ b/mtypedef.cpp @@ -921,7 +921,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_GOODHEARING, MF_ELECTRONIC); mon("secubot", species_robot, 'R', c_dkgray, MS_SMALL, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 7, 19,100,100, 70, 0, 0, 0, 0, 0, 0, 14, 80, 80, 8, + 7, 19,100,100, 70, 0, 0, 0, 0, 0, 0, 14, 80, 80, 2, &mdeath::explode, &mattack::smg, "\ A boxy robot about four feet high. It moves\n\ slowly on a set of treads, and is armed with\n\ diff --git a/newcharacter.cpp b/newcharacter.cpp index 0b8c144aa1..0135492727 100644 --- a/newcharacter.cpp +++ b/newcharacter.cpp @@ -255,8 +255,6 @@ void draw_tabs(WINDOW* w) mvwputch(w, 1,57, c_ltgray, LINE_XOXO); mvwputch(w, 1,73, c_ltgray, LINE_XOXO); } - - int set_stats(WINDOW* w, player *u, int &points) { @@ -533,8 +531,13 @@ int set_traits(WINDOW* w, player *u, int &points) traitmin = PF_SPLIT + 1; traitmax = PF_MAX; mvwprintz(w, 3, 40, c_ltgray, " "); - mvwprintz(w, 3, 40, COL_TR_BAD, "%s costs %d points", - traits[cur_dis].name.c_str(), traits[cur_dis].points); + if (traits[cur_dis].points > 0) + mvwprintz(w, 3, 40, COL_TR_BAD, "%s costs %d points", + traits[cur_dis].name.c_str(), traits[cur_dis].points); + else + mvwprintz(w, 3, 40, COL_TR_GOOD, "%s earns %d points", + traits[cur_adv].name.c_str(), traits[cur_adv].points * -1); + mvwprintz(w, 22, 0, COL_TR_GOOD, "%s", traits[cur_adv].description.c_str()); mvwprintz(w, 22, 0, COL_TR_BAD, "%s", traits[cur_dis].description.c_str()); } if (cur_trait <= traitmin + 7) { diff --git a/omdata.h b/omdata.h index 821000243e..cec98764c2 100644 --- a/omdata.h +++ b/omdata.h @@ -399,6 +399,8 @@ enum omspec_id OMSPEC_OUTPOST, OMSPEC_SILO, OMSPEC_RADIO, + OMSPEC_MANSION, + OMSPEC_MANSION_WILD, OMSPEC_MEGASTORE, OMSPEC_HOSPITAL, OMSPEC_SEWAGE, @@ -454,6 +456,12 @@ const overmap_special overmap_specials[NUM_OMSPECS] = { {ot_radio_tower, 1, 5, 0, 20, mcat_null, 0, 0, 0, 0, &omspec_place::by_highway, 0}, +{ot_mansion_entrance, 0, 8, 0, -1, mcat_null, 0, 0, 0, 0, + &omspec_place::by_highway, mfb(OMS_FLAG_3X3_SECOND)}, + +{ot_mansion_entrance, 0, 4, 10, -1, mcat_null, 0, 0, 0, 0, + &omspec_place::wilderness, mfb(OMS_FLAG_3X3_SECOND)}, + {ot_megastore_entrance, 0, 5, 0, 10, mcat_null, 0, 0, 0, 0, &omspec_place::by_highway, mfb(OMS_FLAG_3X3_SECOND)}, diff --git a/overmap.cpp b/overmap.cpp index eba3b7ded9..e73966f642 100644 --- a/overmap.cpp +++ b/overmap.cpp @@ -990,7 +990,7 @@ point overmap::choose_point(game *g) int dirx, diry; if (ch != ERR) blink = true; // If any input is detected, make the blinkies on - get_direction(dirx, diry, ch); + get_direction(g, dirx, diry, ch); if (dirx != -2 && diry != -2) { cursx += dirx; cursy += diry; diff --git a/player.cpp b/player.cpp index 9eaa174182..0b4ff6acff 100644 --- a/player.cpp +++ b/player.cpp @@ -3381,7 +3381,7 @@ bool player::eat(game *g, int index) } // At this point, we've definitely eaten the item, so use up some turns. if (has_trait(PF_GOURMAND)) - moves -= 150; + moves -= 150; else moves -= 250; // If it's poisonous... poison us. TODO: More several poison effects @@ -3399,8 +3399,7 @@ bool player::eat(game *g, int index) } else if (g->u_see(posx, posy, linet)) g->add_msg("%s eats a %s.", name.c_str(), eaten->tname(g).c_str()); - if (g->itypes[comest->tool]->is_tool() && - g->itypes[comest->tool]->count_by_charges()) + if (g->itypes[comest->tool]->is_tool()) use_charges(comest->tool, 1); // Tools like lighters get used if (comest->stim > 0) { if (comest->stim < 10 && stim < comest->stim) { diff --git a/pldata.h b/pldata.h index 41a5277bf7..acf7574349 100644 --- a/pldata.h +++ b/pldata.h @@ -260,7 +260,7 @@ enum pl_flag { PF_PONDEROUS1, // 10% movement penalty PF_PONDEROUS2, // 20% PF_PONDEROUS3, // 30% - PF_SUNLIGHT_DEPENDANT,// + PF_SUNLIGHT_dependent,// PF_COLDBLOOD,// PF_COLDBLOOD2,// PF_COLDBLOOD3,// @@ -765,14 +765,14 @@ Your muscles are generally slow to move. You run 10%% slower."}, Your muscles are quite slow to move. You run 20%% slower."}, {"Extremely Ponderous", -8, 0, 0, "\ Your muscles are very slow to move. You run 30%% slower."}, -{"Sunlight Dependant", -5, 0, 0, "\ +{"Sunlight dependent", -5, 0, 0, "\ You feel very sluggish when not in direct sunlight. You suffer a 5%% drop in\n\ speed when in shade, and a 10%% drop in speed when in the dark."}, -{"Heat Dependant", -2, 0, 0, "\ -Your muscle response is dependant on ambient temperatures. You lose 1%% of\n\ +{"Heat dependent", -2, 0, 0, "\ +Your muscle response is dependent on ambient temperatures. You lose 1%% of\n\ your speed for every 5 degrees below 65 F."}, -{"Very Heat Dependant", -3, 0, 0, "\ -Your muscle response is highly dependant on ambient temperatures. You lose\n\ +{"Very Heat dependent", -3, 0, 0, "\ +Your muscle response is highly dependent on ambient temperatures. You lose\n\ 1%% of your speed for every 3 degrees below 65 F."}, {"Cold Blooded", -5, 0, 0, "\ You are cold-blooded and rely on heat to keep moving. Your lose 1%% of your\n\ diff --git a/ranged.cpp b/ranged.cpp index cf4bfd8b77..529ff26974 100644 --- a/ranged.cpp +++ b/ranged.cpp @@ -488,7 +488,7 @@ std::vector game::target(int &x, int &y, int lowx, int lowy, int hix, wrefresh(w_status); refresh(); ch = input(); - get_direction(tarx, tary, ch); + get_direction(this, tarx, tary, ch); if (tarx != -2 && tary != -2 && ch != '.') { // Direction character pressed if (m.sees(u.posx, u.posy, x, y, -1, junk)) m.drawsq(w_terrain, u, x, y, false, true); diff --git a/tutorial.cpp b/tutorial.cpp index 4e49878f09..40d88e0480 100644 --- a/tutorial.cpp +++ b/tutorial.cpp @@ -3,7 +3,7 @@ #include "action.h" #include "tutorial.h" -void tutorial_game::init(game *g) +bool tutorial_game::init(game *g) { g->turn = HOURS(12); // Start at noon for (int i = 0; i < NUM_LESSONS; i++) @@ -49,6 +49,8 @@ void tutorial_game::init(game *g) g->levz = 0; g->u.posx = SEEX + 2; g->u.posy = SEEY + 4; + + return true; } void tutorial_game::per_turn(game *g) From f926dc2cd8993daeb94766261a2073669ccaabe1 Mon Sep 17 00:00:00 2001 From: Whales Date: Thu, 26 Apr 2012 20:43:22 -0400 Subject: [PATCH 07/51] Forgot to remove keymap.txt --- data/keymap.txt | 93 ------------------------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 data/keymap.txt diff --git a/data/keymap.txt b/data/keymap.txt deleted file mode 100644 index 11356de10f..0000000000 --- a/data/keymap.txt +++ /dev/null @@ -1,93 +0,0 @@ -# This is the keymapping for Cataclysm. -# You can start a line with # to make it a comment--it will be ignored. -# Blank lines are ignored too. -# Extra whitespace, including tab, is ignored, so format things how you like. - -# The format for each line is an action identifier, followed by several -# keys. Any action may have an unlimited number of keys bound to it. -# If you bind the same key to multiple actions, the second and subsequent -# bindings will be ignored--and you'll get a warning when the game starts. -# Keys are case-sensitive, of course; c and C are different. - -# WARNING: If you skip an action identifier, there will be no key bound to -# that action! You will be NOT be warned of this when the game starts. -# If you're going to mess with stuff, maybe you should back this file up? - -# It is okay to split commands across lines. -# pause . 5 is equivalent to: -# pause . -# pause 5 - -# Note that movement keybindings ONLY apply to movement (for now). -# That is, binding w to move_n will let you use w to move north, but you -# cannot use w to smash north, examine to the north, etc. -# For now, you must use vikeys, the numpad, or arrow keys for those actions. -# This is planned to change in the future. - -# Finally, there is no support for special keys, like spacebar, Home, and -# so on. This is not a planned feature, but if it's important to you, please -# let me know. - -# MOVEMENT: -pause . 5 -move_n k 8 -move_ne u 9 -move_e l 6 -move_se n 3 -move_s j 2 -move_sw b 1 -move_w h 4 -move_nw y 7 -move_down > -move_up < - -# ENVIRONMENT INTERACTION -open o -close c -smash s -examine e -pickup , g -butcher B -chat C -look ; x - -# INVENTORY & QUASI-INVENTORY INTERACTION -inventory i -organize = -apply a -wear W -take_off T -eat E -read R -wield w -reload r -unload U -throw t -fire f -fire_burst F -drop d -drop_adj D -bionics p - -# LONG TERM & SPECIAL ACTIONS -wait ^ -craft & -construct * -sleep $ -safemode ! -autosafe " -ignore_enemy ' -save S -quit Q - -# INFO SCREENS -player_data @ -map m : -factions # -morale % -help ? - -# DEBUG FUNCTIONS -debug Z -debug_scent - -debug_mon ~ From 2818169ec224943bda9af28fc485dbc0bd532e15 Mon Sep 17 00:00:00 2001 From: Whales Date: Thu, 26 Apr 2012 22:38:16 -0400 Subject: [PATCH 08/51] A few more things. --- keypress.cpp | 19 ++++--------------- mutation_data.cpp | 8 ++++---- newcharacter.cpp | 17 ++++------------- player.cpp | 4 ++-- pldata.h | 2 +- 5 files changed, 15 insertions(+), 35 deletions(-) diff --git a/keypress.cpp b/keypress.cpp index fa28921e05..f9cad65e5e 100644 --- a/keypress.cpp +++ b/keypress.cpp @@ -6,21 +6,10 @@ long input() { long ch = getch(); switch (ch) { -/* Legacy - case '7': return 'y'; - case KEY_UP: - case '8': return 'k'; - case '9': return 'u'; - case KEY_LEFT: - case '4': return 'h'; - case '5': return '.'; - case KEY_RIGHT: - case '6': return 'l'; - case '1': return 'b'; - case KEY_DOWN: - case '2': return 'j'; - case '3': return 'n'; -*/ + case KEY_UP: return 'k'; + case KEY_LEFT: return 'h'; + case KEY_RIGHT: return 'l'; + case KEY_DOWN: return 'j'; case 459: return '\n'; default: return ch; } diff --git a/mutation_data.cpp b/mutation_data.cpp index 208f269d8c..0e810a2c10 100644 --- a/mutation_data.cpp +++ b/mutation_data.cpp @@ -341,16 +341,16 @@ void game::init_mutations() PREREQS (PF_SPOTS); MUTATION(PF_TROGLO); - CANCELS (PF_SUNLIGHT_DEPENDANT); + CANCELS (PF_SUNLIGHT_DEPENDENT); CHANGES_TO (PF_TROGLO2); MUTATION(PF_TROGLO2); - CANCELS (PF_SUNLIGHT_DEPENDANT); + CANCELS (PF_SUNLIGHT_DEPENDENT); PREREQS (PF_TROGLO); CHANGES_TO (PF_TROGLO3); MUTATION(PF_TROGLO3); - CANCELS (PF_SUNLIGHT_DEPENDANT); + CANCELS (PF_SUNLIGHT_DEPENDENT); PREREQS (PF_TROGLO2); MUTATION(PF_BEAK); @@ -390,7 +390,7 @@ void game::init_mutations() CANCELS (PF_FLEET, PF_FLEET2); PREREQS (PF_PONDEROUS2); - MUTATION(PF_SUNLIGHT_DEPENDANT); + MUTATION(PF_SUNLIGHT_DEPENDENT); CANCELS (PF_TROGLO, PF_TROGLO2, PF_TROGLO3); MUTATION(PF_GROWL); diff --git a/newcharacter.cpp b/newcharacter.cpp index 0135492727..9e1a73cb26 100644 --- a/newcharacter.cpp +++ b/newcharacter.cpp @@ -514,12 +514,8 @@ int set_traits(WINDOW* w, player *u, int &points) traitmin = 1; traitmax = PF_SPLIT; mvwprintz(w, 3, 40, c_ltgray, " "); - if (traits[cur_adv].points > 0) - mvwprintz(w, 3, 40, COL_TR_GOOD, "%s costs %d points", - traits[cur_adv].name.c_str(), traits[cur_adv].points); - else - mvwprintz(w, 3, 40, COL_TR_GOOD, "%s earns %d points", - traits[cur_adv].name.c_str(), traits[cur_adv].points * -1); + mvwprintz(w, 3, 40, COL_TR_GOOD, "%s costs %d points", + traits[cur_adv].name.c_str(), traits[cur_adv].points); mvwprintz(w, 22, 0, COL_TR_GOOD, "%s", traits[cur_adv].description.c_str()); } else { col_on = COL_TR_BAD_ON; @@ -531,13 +527,8 @@ int set_traits(WINDOW* w, player *u, int &points) traitmin = PF_SPLIT + 1; traitmax = PF_MAX; mvwprintz(w, 3, 40, c_ltgray, " "); - if (traits[cur_dis].points > 0) - mvwprintz(w, 3, 40, COL_TR_BAD, "%s costs %d points", - traits[cur_dis].name.c_str(), traits[cur_dis].points); - else - mvwprintz(w, 3, 40, COL_TR_GOOD, "%s earns %d points", - traits[cur_adv].name.c_str(), traits[cur_adv].points * -1); - mvwprintz(w, 22, 0, COL_TR_GOOD, "%s", traits[cur_adv].description.c_str()); + mvwprintz(w, 3, 40, COL_TR_BAD, "%s earns %d points", + traits[cur_dis].name.c_str(), traits[cur_dis].points * -1); mvwprintz(w, 22, 0, COL_TR_BAD, "%s", traits[cur_dis].description.c_str()); } if (cur_trait <= traitmin + 7) { diff --git a/player.cpp b/player.cpp index 0b4ff6acff..4e4abd470d 100644 --- a/player.cpp +++ b/player.cpp @@ -306,7 +306,7 @@ int player::current_speed(game *g) newmoves = int(newmoves * 1.10); if (g != NULL) { - if (has_trait(PF_SUNLIGHT_DEPENDANT) && !g->is_in_sunlight(posx, posy)) + if (has_trait(PF_SUNLIGHT_DEPENDENT) && !g->is_in_sunlight(posx, posy)) newmoves -= (g->light_level() >= 12 ? 5 : 10); if ((has_trait(PF_COLDBLOOD) || has_trait(PF_COLDBLOOD2) || has_trait(PF_COLDBLOOD3)) && g->temperature < 65) { @@ -885,7 +885,7 @@ Strength - 4; Dexterity - 4; Intelligence - 4; Dexterity - 4"); (pen < 10 ? " " : ""), pen); line++; } - if (has_trait(PF_SUNLIGHT_DEPENDANT) && !g->is_in_sunlight(posx, posy)) { + if (has_trait(PF_SUNLIGHT_DEPENDENT) && !g->is_in_sunlight(posx, posy)) { pen = (g->light_level() >= 12 ? 5 : 10); mvwprintz(w_speed, line, 1, c_red, "Out of Sunlight -%s%d%%%%", (pen < 10 ? " " : ""), pen); diff --git a/pldata.h b/pldata.h index acf7574349..9a978111e2 100644 --- a/pldata.h +++ b/pldata.h @@ -260,7 +260,7 @@ enum pl_flag { PF_PONDEROUS1, // 10% movement penalty PF_PONDEROUS2, // 20% PF_PONDEROUS3, // 30% - PF_SUNLIGHT_dependent,// + PF_SUNLIGHT_DEPENDENT,// PF_COLDBLOOD,// PF_COLDBLOOD2,// PF_COLDBLOOD3,// From 2cdbcd09f756eb4c0d37f205bb22d2897acffff8 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Mon, 30 Apr 2012 20:18:01 -0400 Subject: [PATCH 09/51] Bug fixes --- disease.h | 7 ++++--- game.cpp | 9 +++++++-- item.cpp | 4 ++-- itypedef.cpp | 2 +- iuse.cpp | 2 +- mapgen.cpp | 18 ++++++++--------- monster.cpp | 4 ++-- mtypedef.cpp | 1 + mutation_data.cpp | 4 ++-- player.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++--- 10 files changed, 75 insertions(+), 25 deletions(-) diff --git a/disease.h b/disease.h index 6c2fc64810..3c3acc04ab 100644 --- a/disease.h +++ b/disease.h @@ -727,7 +727,7 @@ void dis_effect(game *g, player &p, disease &dis) beast.spawn(x, y); g->z.push_back(beast); if (g->u_see(x, y, junk)) - g->add_msg("A portal opens nearby, and a monster crawls through!"); + g->cancel_activity_query("A portal opens nearby, and a monster crawls through!"); if (one_in(2)) p.rem_disease(DI_TELEGLOW); } @@ -786,8 +786,9 @@ void dis_effect(game *g, player &p, disease &dis) g->m.ter(x, y) = t_rubble; beast.spawn(x, y); g->z.push_back(beast); - if (g->u_see(x, y, junk)) - g->add_msg("A portal opens nearby, and a monster crawls through!"); + if (g->u_see(x, y, junk)) { + g->cancel_activity_query("A portal opens nearby, and a monster crawls through!"); + } dis.duration /= 2; } } diff --git a/game.cpp b/game.cpp index 9993868e6e..af4d246b5f 100644 --- a/game.cpp +++ b/game.cpp @@ -544,13 +544,15 @@ bool game::do_turn() if ((!u.has_trait(PF_LIGHTEATER) || !one_in(3)) && (!u.has_bionic(bio_recycler) || turn % 300 == 0)) u.hunger++; - if (!u.has_bionic(bio_recycler) || turn % 100 == 0) + if ((!u.has_bionic(bio_recycler) || turn % 100 == 0) && + (!u.has_trait(PF_PLANTSKIN) || !one_in(4))) u.thirst++; u.fatigue++; if (u.fatigue == 192 && !u.has_disease(DI_LYING_DOWN) && !u.has_disease(DI_SLEEP)) { + cancel_activity_query("You're feeling tired."); add_msg("You're feeling tired. Press '$' to lie down for sleep."); - u.activity.type = ACT_NULL; + // u.activity.type = ACT_NULL; } if (u.stim < 0) u.stim++; @@ -1548,6 +1550,9 @@ void game::load(std::string name) u.inv.push_back(item(itemdata, this)); else if (item_place == 'C') u.inv[u.inv.size() - 1].contents.push_back(item(itemdata, this)); + // Preceding line is what confuses containers. We're assuming that + // the most recently-added item is at position (size - 1), but that's not + // true if the empty container stacked with a previous container. else if (item_place == 'W') u.worn.push_back(item(itemdata, this)); else if (item_place == 'w') diff --git a/item.cpp b/item.cpp index a85af72675..9b9bfb6617 100644 --- a/item.cpp +++ b/item.cpp @@ -202,7 +202,7 @@ std::string item::save_info() * curammo isn't NULL. The crashes seem to occur most frequently when saving an * NPC, or when saving map data containing an item an NPC has dropped. */ - if (is_gun() && curammo != NULL) + if (curammo != NULL) ammotmp = curammo->id; if (ammotmp < 0 || ammotmp > num_items) ammotmp = 0; // Saves us from some bugs @@ -257,7 +257,7 @@ void item::load_info(std::string data, game *g) active = false; if (acttmp == 1) active = true; - if (is_gun() && ammotmp > 0) + if (ammotmp > 0) curammo = dynamic_cast(g->itypes[ammotmp]); else curammo = NULL; diff --git a/itypedef.cpp b/itypedef.cpp index 0941426833..f72f4e1932 100644 --- a/itypedef.cpp +++ b/itypedef.cpp @@ -489,7 +489,7 @@ MED("Dayquil", 70, 75, c_yellow, itm_null, Daytime flu medication. Will halt all flu symptoms for a while."); MED("Nyquil", 70, 85, c_blue, itm_null, - PLASTIC, -7, 1, 0, 10, 0,&iuse::flusleep, ADD_NULL, "\ + PLASTIC, -7, 1, 30, 10, 0,&iuse::flusleep, ADD_SLEEP, "\ Nighttime flu medication. Will halt all flu symptoms for a while, plus make\n\ you sleepy."); diff --git a/iuse.cpp b/iuse.cpp index 22a6721f50..dc2b06fa5c 100644 --- a/iuse.cpp +++ b/iuse.cpp @@ -1351,7 +1351,7 @@ That trap needs a 3x3 space to be clear, centered two tiles from you."); g->add_msg(message.str().c_str()); p->practice(sk_traps, practice); g->m.add_trap(posx, posy, type); - p->moves -= practice * 25; + p->moves -= practice * 75; if (type == tr_engine) { for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { diff --git a/mapgen.cpp b/mapgen.cpp index 0e12893790..626994603a 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -2587,8 +2587,8 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, break; case 3: // Supplies for (int i = by1 + 1; i <= by2 - 1; i += 3) { - line(this, t_rack, bx1 + 1, i, bx2 - 1, i); - place_items(mi_mil_food, 78, bx1 + 1, i, bx2 - 1, i, false, 0); + line(this, t_rack, bx1 + 2, i, bx2 - 1, i); + place_items(mi_mil_food, 78, bx1 + 2, i, bx2 - 1, i, false, 0); } break; } @@ -4921,12 +4921,12 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, square(this, t_floor, 1, 11, SEEX * 2 - 1, SEEY * 2 - 2); build_mansion_room(this, room_mansion_entry, 1, 11, SEEX * 2 - 1, SEEY*2 - 2); // Rotate to face the road - if (t_east >= ot_road_null && t_east <= ot_bridge_ew) - rotate(1); - if (t_south >= ot_road_null && t_south <= ot_bridge_ew) - rotate(2); - if (t_west >= ot_road_null && t_west <= ot_bridge_ew) - rotate(3); + // if (t_east >= ot_road_null && t_east <= ot_bridge_ew) + // rotate(1); + // if (t_south >= ot_road_null && t_south <= ot_bridge_ew) + // rotate(2); + // if (t_west >= ot_road_null && t_west <= ot_bridge_ew) + // rotate(3); } break; case ot_mansion: @@ -4949,7 +4949,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, line(this, t_wall_v, SEEX * 2 - 1, 0, SEEX * 2 - 1, SEEX * 2 - 1); } // Now pick a random layout - switch (rng(1, 6)) { + switch (rng(1, 4)) { case 1: // Just one. big. room. mansion_room(this, 1, tw, rw, SEEY * 2 - 2); diff --git a/monster.cpp b/monster.cpp index 7c246830c9..99f7894ed5 100644 --- a/monster.cpp +++ b/monster.cpp @@ -84,8 +84,8 @@ monster::monster(mtype *t, int x, int y) spawnposx = -1; spawnposy = -1; friendly = 0; - anger = 0; - morale = 0; + anger = t->agro; + morale = t->morale; faction_id = -1; mission_id = -1; dead = false; diff --git a/mtypedef.cpp b/mtypedef.cpp index df3b7a231d..60ffd1cfcd 100644 --- a/mtypedef.cpp +++ b/mtypedef.cpp @@ -326,6 +326,7 @@ eyes. As you look at it, you're gripped by a\n\ feeling of dread and terror." ); FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_STUMBLES, MF_WARM, MF_BASHES, MF_POISON); +ANGER(MTRIG_HURT, MTRIG_PLAYER_WEAK); mon("zombie scientist",species_zombie, 'Z',c_ltgray, MS_MEDIUM, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq diff --git a/mutation_data.cpp b/mutation_data.cpp index 0e810a2c10..b0b4a9de37 100644 --- a/mutation_data.cpp +++ b/mutation_data.cpp @@ -160,10 +160,10 @@ void game::init_mutations() PREREQS (PF_PLANTSKIN); MUTATION(PF_THORNS); - PREREQS (PF_PLANTSKIN, PF_BARK); + PREREQS (PF_BARK); MUTATION(PF_LEAVES); - PREREQS (PF_PLANTSKIN, PF_BARK); + PREREQS (PF_BARK); MUTATION(PF_NAILS); CHANGES_TO (PF_CLAWS, PF_TALONS); diff --git a/player.cpp b/player.cpp index 4e4abd470d..aa8bc93a00 100644 --- a/player.cpp +++ b/player.cpp @@ -43,6 +43,7 @@ player::player() cash = 0; recoil = 0; scent = 500; + health = 0; name = ""; male = true; inv_sorted = true; @@ -98,6 +99,7 @@ player& player::operator= (player rhs) inv_sorted = rhs.inv_sorted; moves = rhs.moves; oxygen = rhs.oxygen; + health = rhs.health; active_mission = rhs.active_mission; xp_pool = rhs.xp_pool; for (int i = 0; i < num_skill_types; i++) { @@ -745,6 +747,22 @@ Strength - 4; Dexterity - 4; Intelligence - 4; Dexterity - 4"); status = c_green; mvwprintz(w_stats, 5, (per_cur < 10 ? 17 : 16), status, "%d", per_cur); + if (health <= -20) + status = c_red; + else if (health <= -5) + status = c_ltred; + else if (health <= 5) + status = c_white; + else if (health <= 30) + status = c_ltgreen; + else + status = c_green; + mvwprintz(w_stats, 6, 2, status, "Health:%s%d", + abs(health) >= 100 ? " " : + abs(health) >= 10 ? " " : " ", + health); + + wrefresh(w_stats); // Next, draw encumberment. @@ -2411,8 +2429,10 @@ void player::suffer(game *g) g->add_msg("Your asthma wakes you up!"); auto_use = false; } - if (auto_use) + if (auto_use) { + g->add_msg("You take a puff of your inhaler."); use_charges(itm_inhaler, 1); + } else { add_disease(DI_ASTHMA, 50 * rng(1, 4), g); g->cancel_activity_query("You have an asthma attack!"); @@ -2709,10 +2729,10 @@ void player::sort_inv() types[1].push_back(tmp); else if (tmp[0].is_armor()) types[3].push_back(tmp); - else if (tmp[0].is_tool() || tmp[0].is_gunmod()) - types[5].push_back(tmp); else if (tmp[0].is_food() || tmp[0].is_food_container()) types[4].push_back(tmp); + else if (tmp[0].is_tool() || tmp[0].is_gunmod()) + types[5].push_back(tmp); else if (tmp[0].is_book()) types[6].push_back(tmp); else if (tmp[0].is_weap()) @@ -2751,6 +2771,24 @@ void player::i_add(item it) if (it.charges > 0) inv.push_back(it); return; + } + if (it.is_food() && it.charges != -1) { // Possibly combine with other food + for (int i = 0; i < inv.size(); i++) { + if (inv[i].type->id == it.type->id) { + it_comest* food = dynamic_cast(inv[i].type); + if (inv[i].charges < food->charges) { + inv[i].charges += it.charges; + if (inv[i].charges > food->charges) { + it.charges = inv[i].charges - food->charges; + inv[i].charges = food->charges; + } else + it.charges = 0; + } + } + } + if (it.charges > 0) + inv.push_back(it); + return; } inv.push_back(it); } @@ -3948,6 +3986,11 @@ void player::read(game *g, char ch) void player::try_to_sleep(game *g) { + if (g->m.ter(posx, posy) == t_bed) + g->add_msg("This bed is a comfortable place to sleep."); + else if (g->m.ter(posx, posy) != t_floor) + g->add_msg("It's %shard to get to sleep on this %s.", terlist[g->m.ter(posx, posy)].movecost <= 2 ? "a little " : "", terlist[g->m.ter(posx, posy)].name.c_str()); + add_disease(DI_LYING_DOWN, 300, g); } From b5aa6a135af120ad21009652bd12a11b098301bf Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Wed, 2 May 2012 15:19:27 -0400 Subject: [PATCH 10/51] Started Journey mode --- game.cpp | 16 +- game.h | 5 +- gamemode.cpp | 4 + gamemode.h | 11 +- morale.h | 5 +- omdata.h | 2 +- player.cpp | 1 + west.cpp | 1395 ++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1431 insertions(+), 8 deletions(-) create mode 100644 west.cpp diff --git a/game.cpp b/game.cpp index af4d246b5f..c07b7a730c 100644 --- a/game.cpp +++ b/game.cpp @@ -1734,11 +1734,13 @@ void game::debug() case 7: popup_top("\ -Location %d:%d in %d:%d, %s\n\ +Location %d:%d in %d:%d in %d:%d, %s\n\ +Global Location %d:%d\n\ Current turn: %d; Next spawn %d.\n\ %d monsters exist.\n\ -%d events planned.", u.posx, u.posy, levx, levy, +%d events planned.", u.posx, u.posy, levx, levy, cur_om.posx, cur_om.posy, oterlist[cur_om.ter(levx / 2, levy / 2)].name.c_str(), + global_location().x, global_location().y, int(turn), int(nextspawn), z.size(), events.size()); if (!active_npc.empty()) popup_top("\%s: %d:%d (you: %d:%d)", active_npc[0].name.c_str(), @@ -3025,7 +3027,7 @@ void game::explosion(int x, int y, int power, int shrapnel, bool fire) m.destroy(this, i, j, false); int mon_hit = mon_at(i, j), npc_hit = npc_at(i, j); - if (mon_hit != -1 && z[mon_hit].hurt(rng(dam / 2, dam * 1.5))) { + if (mon_hit != -1 && !z[mon_hit].dead && z[mon_hit].hurt(rng(dam / 2, dam * 1.5))) { if (z[mon_hit].hp < 0 - 1.5 * z[mon_hit].type->hp) explode_mon(mon_hit); // Explode them if it was big overkill else @@ -5629,6 +5631,14 @@ point game::om_location() return ret; } +point game::global_location() +{ + point ret; + ret.x = cur_om.posx*OMAPX + om_location().x; + ret.y = cur_om.posy*OMAPY + om_location().y; + return ret; +} + void game::replace_stair_monsters() { for (int i = 0; i < coming_to_stairs.size(); i++) diff --git a/game.h b/game.h index 5c938bca63..61ab02e7aa 100644 --- a/game.h +++ b/game.h @@ -143,6 +143,7 @@ class game void update_map(int &x, int &y); // Called by plmove when the map updates void update_overmap_seen(); // Update which overmap tiles we can see point om_location(); // levx and levy converted to overmap coordinates + point global_location(); // current terrain-grid location on global coordinate system faction* random_good_faction(); faction* random_evil_faction(); @@ -198,7 +199,6 @@ class game WINDOW *w_messages; WINDOW *w_status; - private: // Game-start procedures bool opening_screen();// Warn about screen size, then present the main menu bool load_master(); // Load the master data file, with factions &c @@ -206,6 +206,7 @@ class game void start_game(); // Starts a new game void start_special_game(special_game_id gametype); // See gamemode.cpp + private: // Data Initialization void init_itypes(); // Initializes item types void init_mapitems(); // Initializes item placement @@ -276,10 +277,12 @@ class game void replace_stair_monsters(); void update_stair_monsters(); void spawn_mon(int shift, int shifty); // Called by update_map, sometimes + public: mon_id valid_monster_from(std::vector group); int valid_group(mon_id type, int x, int y);// Picks a group from cur_om moncat_id mt_to_mc(mon_id type);// Monster type to monster category void set_adjacent_overmaps(bool from_scratch = false); + private: // Routine loop functions, approximately in order of execution void monmove(); // Monster movement diff --git a/gamemode.cpp b/gamemode.cpp index 96ed75918a..a9e29e5077 100644 --- a/gamemode.cpp +++ b/gamemode.cpp @@ -8,6 +8,7 @@ std::string special_game_name(special_game_id id) case NUM_SPECIAL_GAMES: return "nethack (this is a bug)"; case SGAME_TUTORIAL: return "Tutorial"; case SGAME_DEFENSE: return "Defense"; + case SGAME_WEST: return "Journey to the West"; default: return "Uncoded (this is a bug)"; } } @@ -25,6 +26,9 @@ special_game* get_special_game(special_game_id id) case SGAME_DEFENSE: ret = new defense_game; break; + case SGAME_WEST: + ret = new west_game; + break; default: debugmsg("Missing something in get_special_game()?"); ret = new special_game; diff --git a/gamemode.h b/gamemode.h index d3a3d30b16..d95dc226f3 100644 --- a/gamemode.h +++ b/gamemode.h @@ -14,13 +14,14 @@ enum special_game_id { SGAME_NULL = 0, SGAME_TUTORIAL, SGAME_DEFENSE, +SGAME_WEST, NUM_SPECIAL_GAMES }; std::string special_game_name(special_game_id id); special_game* get_special_game(special_game_id id); -struct special_game +struct special_game { virtual special_game_id id() { return SGAME_NULL; }; // init is run when the game begins @@ -179,4 +180,12 @@ struct defense_game : public special_game }; +struct west_game : public special_game { + int horde_location; + virtual bool init(game *g); + virtual void per_turn(game * g); + int distance_to_horde(game *g); + +}; + #endif diff --git a/morale.h b/morale.h index 334119e742..9c02111acc 100644 --- a/morale.h +++ b/morale.h @@ -39,9 +39,10 @@ struct morale_point morale_type type; itype* item_type; int bonus; + int halflife; - morale_point(morale_type T = MORALE_NULL, itype* I = NULL, int B = 0) : - type (T), item_type (I), bonus (B) {}; +morale_point(morale_type T = MORALE_NULL, itype* I = NULL, int B = 0, int H = 15) : + type (T), item_type (I), bonus (B), halflife (H) {}; std::string name(std::string morale_data[]) { diff --git a/omdata.h b/omdata.h index cec98764c2..9e5a38d61c 100644 --- a/omdata.h +++ b/omdata.h @@ -10,7 +10,7 @@ #include "mongroup.h" #include "mapdata.h" -#define OMAPX 180 +#define OMAPX 180 // Number of overmap tiles per overmap. #define OMAPY 180 #define TUTORIAL_Z 10 diff --git a/player.cpp b/player.cpp index aa8bc93a00..6a353c7395 100644 --- a/player.cpp +++ b/player.cpp @@ -52,6 +52,7 @@ player::player() active_mission = -1; xp_pool = 0; last_item = itype_id(itm_null); + for (int i = 0; i < num_skill_types; i++) { sklevel[i] = 0; skexercise[i] = 0; diff --git a/west.cpp b/west.cpp new file mode 100644 index 0000000000..1450b7f3de --- /dev/null +++ b/west.cpp @@ -0,0 +1,1395 @@ +#include "gamemode.h" +#include "game.h" +#include "setvector.h" +#include "keypress.h" +#include "itype.h" +#include "mtype.h" +#include +#include +#include + +// #define SPECIAL_WAVE_CHANCE 5 // One in X chance of single-flavor wave +// #define SPECIAL_WAVE_MIN 5 // Don't use a special wave with < X monsters + +// #define SELCOL(n) (selection == (n) ? c_yellow : c_blue) +// #define TOGCOL(n, b) (selection == (n) ? (b ? c_ltgreen : c_yellow) :\ +// (b ? c_green : c_dkgray)) +// #define NUMALIGN(n) ((n) >= 10000 ? 20 : ((n) >= 1000 ? 21 :\ +// ((n) >= 100 ? 22 : ((n) >= 10 ? 23 : 24)))) + +// std::string caravan_category_name(caravan_category cat); +// std::vector caravan_items(caravan_category cat); + +// int caravan_price(player &u, int price); + +// void draw_caravan_borders(WINDOW *w, int current_window); +// void draw_caravan_categories(WINDOW *w, int category_selected, int total_price, +// int cash); +// void draw_caravan_items(WINDOW *w, game *g, std::vector *items, +// std::vector *counts, int offset, +// int item_selected); + +// std::string defense_style_name(defense_style style); +// std::string defense_style_description(defense_style style); +// std::string defense_location_name(defense_location location); +// std::string defense_location_description(defense_location location); + +// defense_game::defense_game() +// { +// current_wave = 0; +// hunger = false; +// thirst = false; +// sleep = false; +// zombies = false; +// specials = false; +// spiders = false; +// triffids = false; +// robots = false; +// subspace = false; +// mercenaries = false; +// init_to_style(DEFENSE_EASY); +// } + +bool west_game::init (game *g) +{ + // g->start_game(); + g->turn = MINUTES(STARTING_MINUTES + 300);// It's turn 0... + // run_mode = 1; // run_mode is on by default... + // g->mostseen = 0; // ...and mostseen is 0, we haven't seen any monsters yet. + +// Init some factions. + // if (!g->load_master()) // Master data record contains factions. + // g->create_factions(); + g->cur_om = overmap(g, 0, 0, 0); // We start in the (0,0,0) overmap. +// Find a random house on the map, and set us there. + g->cur_om.first_house(g->levx, g->levy); + g->levx -= int(int(MAPSIZE / 2) / 2); + g->levy -= int(int(MAPSIZE / 2) / 2); + g->levz = 0; +// Start the overmap out with none of it seen by the player... + for (int i = 0; i < OMAPX; i++) { + for (int j = 0; j < OMAPX; j++) + g->cur_om.seen(i, j) = false; + } +// ...except for our immediate neighborhood. + for (int i = -15; i <= 15; i++) { + for (int j = -15; j <= 15; j++) + g->cur_om.seen(g->levx + i, g->levy + j) = true; + } +// Convert the overmap coordinates to submap coordinates + g->levx = g->levx * 2 - 1; + g->levy = g->levy * 2 - 1; + g->set_adjacent_overmaps(true); +// Init the starting map at this location. + g->m.load(g, g->levx, g->levy); +// Start us off somewhere in the shelter. + g->u.create(g, PLTYPE_CUSTOM); + g->u.posx = SEEX * int(MAPSIZE / 2) + 5; + g->u.posy = SEEY * int(MAPSIZE / 2) + 5; + g->u.str_cur = g->u.str_max; + g->u.per_cur = g->u.per_max; + g->u.int_cur = g->u.int_max; + g->u.dex_cur = g->u.dex_max; + // g->nextspawn = int(g->turn); + g->temperature = 65; // Springtime-appropriate? + + g->u.normalize(g); + + horde_location = g->global_location().x - 10; + + return true; +} + +int west_game::distance_to_horde(game *g) { + return g->global_location().x - horde_location; +} + +// bool defense_game::init(game *g) +// { +// g->turn = HOURS(12); // Start at noon +// g->temperature = 65; +// if (!g->u.create(g, PLTYPE_CUSTOM)) +// return false; +// g->u.str_cur = g->u.str_max; +// g->u.per_cur = g->u.per_max; +// g->u.int_cur = g->u.int_max; +// g->u.dex_cur = g->u.dex_max; +// init_itypes(g); +// init_mtypes(g); +// init_constructions(g); +// init_recipes(g); +// current_wave = 0; +// hunger = false; +// thirst = false; +// sleep = false; +// zombies = false; +// specials = false; +// spiders = false; +// triffids = false; +// robots = false; +// subspace = false; +// mercenaries = false; +// init_to_style(DEFENSE_EASY); +// setup(); +// g->u.cash = initial_cash; +// popup_nowait("Please wait as the map generates [ 0%]"); +// init_map(g); +// caravan(g); +// return true; +// } + +void west_game::per_turn(game *g) +{ + if (int(g->turn) % 300 == 0) { + horde_location++; + int dh = distance_to_horde(g); + int monx, mony; + if (dh <= 0) { + for (int i = 0; i < 200; i++) { + mon_id type = g->valid_monster_from(g->moncats[mcat_zombie]); + if (type != mon_null) { + monx = rng(0, SEEX * MAPSIZE - 1); + mony = rng(0, SEEY * MAPSIZE - 1); + monster zom = monster(g->mtypes[type]); + zom.spawn(monx, mony); + zom.wandx = g->u.posx; + zom.wandy = g->u.posy; + g->z.push_back(zom); + } + } + g->add_msg("The horde is upon you!!"); + } + else if (dh < 20) { + g->add_msg("The horde is only %d map squares away!", distance_to_horde(g)); + } + else { + g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); + } + } +} +// if (!thirst) +// g->u.thirst = 0; +// if (!hunger) +// g->u.hunger = 0; +// if (!sleep) +// g->u.fatigue = 0; +// if (int(g->turn) % (time_between_waves * 10) == 0) { +// current_wave++; +// if (current_wave > 1 && current_wave % waves_between_caravans == 0) { +// popup("A caravan approaches! Press spacebar..."); +// caravan(g); +// } +// spawn_wave(g); +// } +// } + +// void defense_game::pre_action(game *g, action_id &act) +// { +// if (act == ACTION_SLEEP && !sleep) { +// g->add_msg("You don't need to sleep!"); +// act = ACTION_NULL; +// } +// if (act == ACTION_SAVE) { +// g->add_msg("You cannot save in defense mode!"); +// act = ACTION_NULL; +// } + +// // Big ugly block for movement +// if ((act == ACTION_MOVE_N && g->u.posy == SEEX * int(MAPSIZE / 2) && +// g->levy <= 93) || +// (act == ACTION_MOVE_NE && ((g->u.posy == SEEY * int(MAPSIZE / 2) && +// g->levy <= 93) || +// (g->u.posx == SEEX * (1 + int(MAPSIZE / 2))-1 && +// g->levx >= 98))) || +// (act == ACTION_MOVE_E && g->u.posx == SEEX * (1 + int(MAPSIZE / 2)) - 1 && +// g->levx >= 98) || +// (act == ACTION_MOVE_SE && ((g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && +// g->levy >= 98) || +// (g->u.posx == SEEX * (1 + int(MAPSIZE / 2))-1 && +// g->levx >= 98))) || +// (act == ACTION_MOVE_S && g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && +// g->levy >= 98) || +// (act == ACTION_MOVE_SW && ((g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && +// g->levy >= 98) || +// (g->u.posx == SEEX * int(MAPSIZE / 2) && +// g->levx <= 93))) || +// (act == ACTION_MOVE_W && g->u.posx == SEEX * int(MAPSIZE / 2) && +// g->levx <= 93) || +// (act == ACTION_MOVE_NW && ((g->u.posy == SEEY * int(MAPSIZE / 2) && +// g->levy <= 93) || +// (g->u.posx == SEEX * int(MAPSIZE / 2) && +// g->levx <= 93)))) { +// g->add_msg("You cannot leave the %s behind!", +// defense_location_name(location).c_str()); +// act = ACTION_NULL; +// } +// } + +// void defense_game::post_action(game *g, action_id act) +// { +// } + +// void defense_game::game_over(game *g) +// { +// popup("You managed to survive through wave %d!", current_wave); +// } + +// void defense_game::init_itypes(game *g) +// { +// g->itypes[itm_2x4]->volume = 0; +// g->itypes[itm_2x4]->weight = 0; +// g->itypes[itm_landmine]->price = 300; +// g->itypes[itm_bot_turret]->price = 6000; +// } + +// void defense_game::init_mtypes(game *g) +// { +// for (int i = 0; i < num_monsters; i++) { +// g->mtypes[i]->difficulty *= 1.5; +// g->mtypes[i]->difficulty += int(g->mtypes[i]->difficulty / 5); +// g->mtypes[i]->flags.push_back(MF_BASHES); +// g->mtypes[i]->flags.push_back(MF_SMELLS); +// g->mtypes[i]->flags.push_back(MF_HEARS); +// g->mtypes[i]->flags.push_back(MF_SEES); +// } +// } + +// void defense_game::init_constructions(game *g) +// { +// for (int i = 0; i < g->constructions.size(); i++) { +// for (int j = 0; j < g->constructions[i]->stages.size(); j++) { +// g->constructions[i]->stages[j].time = 1; // Everything takes 1 minute +// } +// } +// } + +// void defense_game::init_recipes(game *g) +// { +// for (int i = 0; i < g->recipes.size(); i++) { +// g->recipes[i]->time /= 10; // Things take turns, not minutes +// } +// } + +// void defense_game::init_map(game *g) +// { +// for (int x = 0; x < OMAPX; x++) { +// for (int y = 0; y < OMAPY; y++) { +// g->cur_om.ter(x, y) = ot_field; +// g->cur_om.seen(x, y) = true; +// } +// } + +// g->cur_om.save(g->u.name, 0, 0, DEFENSE_Z); +// g->levx = 100; +// g->levy = 100; +// g->levz = 0; +// g->u.posx = SEEX; +// g->u.posy = SEEY; + +// switch (location) { + +// case DEFLOC_HOSPITAL: +// for (int x = 49; x <= 51; x++) { +// for (int y = 49; y <= 51; y++) +// g->cur_om.ter(x, y) = ot_hospital; +// } +// g->cur_om.ter(50, 49) = ot_hospital_entrance; +// break; + +// case DEFLOC_MALL: +// for (int x = 49; x <= 51; x++) { +// for (int y = 49; y <= 51; y++) +// g->cur_om.ter(x, y) = ot_megastore; +// } +// g->cur_om.ter(50, 49) = ot_megastore_entrance; +// break; + +// case DEFLOC_BAR: +// g->cur_om.ter(50, 50) = ot_bar_north; +// break; + +// case DEFLOC_MANSION: +// for (int x = 49; x <= 51; x++) { +// for (int y = 49; y <= 51; y++) +// g->cur_om.ter(x, y) = ot_mansion; +// } +// g->cur_om.ter(50, 49) = ot_mansion_entrance; +// break; +// } +// // Init the map +// int old_percent = 0; +// for (int i = 0; i <= MAPSIZE * 2; i += 2) { +// for (int j = 0; j <= MAPSIZE * 2; j += 2) { +// int mx = g->levx - MAPSIZE + i, my = g->levy - MAPSIZE + j; +// int percent = 100 * ((j / 2 + MAPSIZE * (i / 2))) / +// ((MAPSIZE) * (MAPSIZE + 1)); +// if (percent >= old_percent + 1) { +// popup_nowait("Please wait as the map generates [%s%d%]", +// (percent < 10 ? " " : ""), percent); +// old_percent = percent; +// } +// // Round down to the nearest even number +// mx -= mx % 2; +// my -= my % 2; +// tinymap tm(&g->itypes, &g->mapitems, &g->traps); +// tm.generate(g, &(g->cur_om), mx, my, int(g->turn)); +// tm.clear_spawns(); +// tm.clear_traps(); +// tm.save(&g->cur_om, int(g->turn), mx, my); +// } +// } + +// g->m.load(g, g->levx, g->levy); + +// g->update_map(g->u.posx, g->u.posy); +// monster generator(g->mtypes[mon_generator], g->u.posx + 1, g->u.posy + 1); +// // Find a valid spot to spawn the generator +// std::vector valid; +// for (int x = g->u.posx - 1; x <= g->u.posx + 1; x++) { +// for (int y = g->u.posy - 1; y <= g->u.posy + 1; y++) { +// if (generator.can_move_to(g->m, x, y) && g->is_empty(x, y)) +// valid.push_back( point(x, y) ); +// } +// } +// if (!valid.empty()) { +// point p = valid[rng(0, valid.size() - 1)]; +// generator.spawn(p.x, p.y); +// } +// generator.friendly = -1; +// g->z.push_back(generator); +// } + +// void defense_game::init_to_style(defense_style new_style) +// { +// style = new_style; +// hunger = false; +// thirst = false; +// sleep = false; +// zombies = false; +// specials = false; +// spiders = false; +// triffids = false; +// robots = false; +// subspace = false; +// mercenaries = false; + +// switch (new_style) { +// case DEFENSE_EASY: +// location = DEFLOC_HOSPITAL; +// initial_difficulty = 15; +// wave_difficulty = 10; +// time_between_waves = 30; +// waves_between_caravans = 3; +// initial_cash = 10000; +// cash_per_wave = 1000; +// cash_increase = 300; +// specials = true; +// spiders = true; +// triffids = true; +// mercenaries = true; +// break; + +// case DEFENSE_MEDIUM: +// location = DEFLOC_MALL; +// initial_difficulty = 30; +// wave_difficulty = 15; +// time_between_waves = 20; +// waves_between_caravans = 4; +// initial_cash = 6000; +// cash_per_wave = 800; +// cash_increase = 200; +// specials = true; +// spiders = true; +// triffids = true; +// robots = true; +// hunger = true; +// mercenaries = true; +// break; + +// case DEFENSE_HARD: +// location = DEFLOC_BAR; +// initial_difficulty = 50; +// wave_difficulty = 20; +// time_between_waves = 10; +// waves_between_caravans = 5; +// initial_cash = 2000; +// cash_per_wave = 600; +// cash_increase = 100; +// specials = true; +// spiders = true; +// triffids = true; +// robots = true; +// subspace = true; +// hunger = true; +// thirst = true; +// break; + +// break; + +// case DEFENSE_SHAUN: +// location = DEFLOC_BAR; +// initial_difficulty = 30; +// wave_difficulty = 15; +// time_between_waves = 5; +// waves_between_caravans = 6; +// initial_cash = 5000; +// cash_per_wave = 500; +// cash_increase = 100; +// zombies = true; +// break; + +// case DEFENSE_DAWN: +// location = DEFLOC_MALL; +// initial_difficulty = 60; +// wave_difficulty = 20; +// time_between_waves = 30; +// waves_between_caravans = 4; +// initial_cash = 8000; +// cash_per_wave = 500; +// cash_increase = 0; +// zombies = true; +// hunger = true; +// thirst = true; +// mercenaries = true; +// break; + +// case DEFENSE_SPIDERS: +// location = DEFLOC_MALL; +// initial_difficulty = 60; +// wave_difficulty = 10; +// time_between_waves = 10; +// waves_between_caravans = 4; +// initial_cash = 6000; +// cash_per_wave = 500; +// cash_increase = 100; +// spiders = true; +// break; + +// case DEFENSE_TRIFFIDS: +// location = DEFLOC_MANSION; +// initial_difficulty = 60; +// wave_difficulty = 20; +// time_between_waves = 30; +// waves_between_caravans = 2; +// initial_cash = 10000; +// cash_per_wave = 600; +// cash_increase = 100; +// triffids = true; +// hunger = true; +// thirst = true; +// sleep = true; +// mercenaries = true; +// break; + +// case DEFENSE_SKYNET: +// location = DEFLOC_HOSPITAL; +// initial_difficulty = 20; +// wave_difficulty = 20; +// time_between_waves = 20; +// waves_between_caravans = 6; +// initial_cash = 12000; +// cash_per_wave = 1000; +// cash_increase = 200; +// robots = true; +// hunger = true; +// thirst = true; +// mercenaries = true; +// break; + +// case DEFENSE_LOVECRAFT: +// location = DEFLOC_MANSION; +// initial_difficulty = 20; +// wave_difficulty = 20; +// time_between_waves = 120; +// waves_between_caravans = 8; +// initial_cash = 4000; +// cash_per_wave = 1000; +// cash_increase = 100; +// subspace = true; +// hunger = true; +// thirst = true; +// sleep = true; +// } +// } + +// void defense_game::setup() +// { +// WINDOW* w = newwin(25, 80, 0, 0); +// int selection = 1; +// refresh_setup(w, selection); + +// while (true) { +// char ch = input(); + +// if (ch == 'S') { +// if (!zombies && !specials && !spiders && !triffids && !robots && !subspace) { +// popup("You must choose at least one monster group!"); +// refresh_setup(w, selection); +// } else +// return; +// } else if (ch == '+' || ch == '>' || ch == 'j') { +// if (selection == 19) +// selection = 1; +// else +// selection++; +// refresh_setup(w, selection); +// } else if (ch == '-' || ch == '<' || ch == 'k') { +// if (selection == 1) +// selection = 19; +// else +// selection--; +// refresh_setup(w, selection); +// } else if (ch == '!') { +// std::string name = string_input_popup(20, "Template Name:"); +// refresh_setup(w, selection); +// } else if (ch == 'S') +// return; + +// else { +// switch (selection) { +// case 1: // Scenario selection +// if (ch == 'l') { +// if (style == defense_style(NUM_DEFENSE_STYLES - 1)) +// style = defense_style(1); +// else +// style = defense_style(style + 1); +// } +// if (ch == 'h') { +// if (style == defense_style(1)) +// style = defense_style(NUM_DEFENSE_STYLES - 1); +// else +// style = defense_style(style - 1); +// } +// init_to_style(style); +// break; + +// case 2: // Location selection +// if (ch == 'l') { +// if (location == defense_location(NUM_DEFENSE_LOCATIONS - 1)) +// location = defense_location(1); +// else +// location = defense_location(location + 1); +// } +// if (ch == 'h') { +// if (location == defense_location(1)) +// location = defense_location(NUM_DEFENSE_LOCATIONS - 1); +// else +// location = defense_location(location - 1); +// } +// mvwprintz(w, 5, 2, c_black, "\ +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +// mvwprintz(w, 5, 2, c_yellow, defense_location_name(location).c_str()); +// mvwprintz(w, 5, 28, c_ltgray, +// defense_location_description(location).c_str()); +// break; + +// case 3: // Difficulty of the first wave +// if (ch == 'h' && initial_difficulty > 10) +// initial_difficulty -= 5; +// if (ch == 'l' && initial_difficulty < 995) +// initial_difficulty += 5; +// mvwprintz(w, 7, 22, c_black, "xxx"); +// mvwprintz(w, 7, NUMALIGN(initial_difficulty), c_yellow, "%d", +// initial_difficulty); +// break; + +// case 4: // Wave Difficulty +// if (ch == 'h' && wave_difficulty > 10) +// wave_difficulty -= 5; +// if (ch == 'l' && wave_difficulty < 995) +// wave_difficulty += 5; +// mvwprintz(w, 8, 22, c_black, "xxx"); +// mvwprintz(w, 8, NUMALIGN(wave_difficulty), c_yellow, "%d", +// wave_difficulty); +// break; + +// case 5: +// if (ch == 'h' && time_between_waves > 5) +// time_between_waves -= 5; +// if (ch == 'l' && time_between_waves < 995) +// time_between_waves += 5; +// mvwprintz(w, 10, 22, c_black, "xxx"); +// mvwprintz(w, 10, NUMALIGN(time_between_waves), c_yellow, "%d", +// time_between_waves); +// break; + +// case 6: +// if (ch == 'h' && waves_between_caravans > 1) +// waves_between_caravans -= 1; +// if (ch == 'l' && waves_between_caravans < 50) +// waves_between_caravans += 1; +// mvwprintz(w, 11, 22, c_black, "xxx"); +// mvwprintz(w, 11, NUMALIGN(waves_between_caravans), c_yellow, "%d", +// waves_between_caravans); +// break; + +// case 7: +// if (ch == 'h' && initial_cash > 0) +// initial_cash -= 100; +// if (ch == 'l' && initial_cash < 99900) +// initial_cash += 100; +// mvwprintz(w, 13, 20, c_black, "xxxxx"); +// mvwprintz(w, 13, NUMALIGN(initial_cash), c_yellow, "%d", initial_cash); +// break; + +// case 8: +// if (ch == 'h' && cash_per_wave > 0) +// cash_per_wave -= 100; +// if (ch == 'l' && cash_per_wave < 9900) +// cash_per_wave += 100; +// mvwprintz(w, 14, 21, c_black, "xxxx"); +// mvwprintz(w, 14, NUMALIGN(cash_per_wave), c_yellow, "%d", cash_per_wave); +// break; + +// case 9: +// if (ch == 'h' && cash_increase > 0) +// cash_increase -= 50; +// if (ch == 'l' && cash_increase < 9950) +// cash_increase += 50; +// mvwprintz(w, 15, 21, c_black, "xxxx"); +// mvwprintz(w, 15, NUMALIGN(cash_increase), c_yellow, "%d", cash_increase); +// break; + +// case 10: +// if (ch == ' ' || ch == '\n') { +// zombies = !zombies; +// specials = false; +// } +// mvwprintz(w, 18, 2, (zombies ? c_ltgreen : c_yellow), "Zombies"); +// mvwprintz(w, 18, 14, c_yellow, "Special Zombies"); +// break; + +// case 11: +// if (ch == ' ' || ch == '\n') { +// specials = !specials; +// zombies = false; +// } +// mvwprintz(w, 18, 2, c_yellow, "Zombies"); +// mvwprintz(w, 18, 14, (specials ? c_ltgreen : c_yellow), "Special Zombies"); +// break; + +// case 12: +// if (ch == ' ' || ch == '\n') +// spiders = !spiders; +// mvwprintz(w, 18, 34, (spiders ? c_ltgreen : c_yellow), "Spiders"); +// break; + +// case 13: +// if (ch == ' ' || ch == '\n') +// triffids = !triffids; +// mvwprintz(w, 18, 46, (triffids ? c_ltgreen : c_yellow), "Triffids"); +// break; + +// case 14: +// if (ch == ' ' || ch == '\n') +// robots = !robots; +// mvwprintz(w, 18, 59, (robots ? c_ltgreen : c_yellow), "Robots"); +// break; + +// case 15: +// if (ch == ' ' || ch == '\n') +// subspace = !subspace; +// mvwprintz(w, 18, 70, (subspace ? c_ltgreen : c_yellow), "Subspace"); +// break; + +// case 16: +// if (ch == ' ' || ch == '\n') +// hunger = !hunger; +// mvwprintz(w, 21, 2, (hunger ? c_ltgreen : c_yellow), "Food"); +// break; + +// case 17: +// if (ch == ' ' || ch == '\n') +// thirst = !thirst; +// mvwprintz(w, 21, 16, (thirst ? c_ltgreen : c_yellow), "Water"); +// break; + +// case 18: +// if (ch == ' ' || ch == '\n') +// sleep = !sleep; +// mvwprintz(w, 21, 31, (sleep ? c_ltgreen : c_yellow), "Sleep"); +// break; + +// case 19: +// if (ch == ' ' || ch == '\n') +// mercenaries = !mercenaries; +// mvwprintz(w, 21, 46, (mercenaries ? c_ltgreen : c_yellow), "Mercenaries"); +// break; +// } +// } +// if (ch == 'h' || ch == 'l' || ch == ' ' || ch == '\n') +// refresh_setup(w, selection); +// } +// } + +// void defense_game::refresh_setup(WINDOW* w, int selection) +// { +// werase(w); +// mvwprintz(w, 0, 1, c_ltred, "DEFENSE MODE"); +// mvwprintz(w, 0, 28, c_ltred, "Press +/- or >/< to cycle, spacebar to toggle"); +// mvwprintz(w, 1, 28, c_ltred, "Press S to start, ! to save as a template"); +// mvwprintz(w, 2, 2, c_ltgray, "Scenario:"); +// mvwprintz(w, 3, 2, SELCOL(1), defense_style_name(style).c_str()); +// mvwprintz(w, 3, 28, c_ltgray, defense_style_description(style).c_str()); +// mvwprintz(w, 4, 2, c_ltgray, "Location:"); +// mvwprintz(w, 5, 2, SELCOL(2), defense_location_name(location).c_str()); +// mvwprintz(w, 5, 28, c_ltgray, defense_location_description(location).c_str()); + +// mvwprintz(w, 7, 2, c_ltgray, "Initial Difficulty:"); +// mvwprintz(w, 7, NUMALIGN(initial_difficulty), SELCOL(3), "%d", +// initial_difficulty); +// mvwprintz(w, 7, 28, c_ltgray, "The difficulty of the first wave."); +// mvwprintz(w, 8, 2, c_ltgray, "Wave Difficulty:"); +// mvwprintz(w, 8, NUMALIGN(wave_difficulty), SELCOL(4), "%d", wave_difficulty); +// mvwprintz(w, 8, 28, c_ltgray, "The increase of difficulty with each wave."); + +// mvwprintz(w, 10, 2, c_ltgray, "Time b/w Waves:"); +// mvwprintz(w, 10, NUMALIGN(time_between_waves), SELCOL(5), "%d", +// time_between_waves); +// mvwprintz(w, 10, 28, c_ltgray, "The time, in minutes, between waves."); +// mvwprintz(w, 11, 2, c_ltgray, "Waves b/w Caravans:"); +// mvwprintz(w, 11, NUMALIGN(waves_between_caravans), SELCOL(6), "%d", +// waves_between_caravans); +// mvwprintz(w, 11, 28, c_ltgray, "The number of waves in between caravans."); + +// mvwprintz(w, 13, 2, c_ltgray, "Initial Cash:"); +// mvwprintz(w, 13, NUMALIGN(initial_cash), SELCOL(7), "%d", initial_cash); +// mvwprintz(w, 13, 28, c_ltgray, "The amount of money the player starts with."); +// mvwprintz(w, 14, 2, c_ltgray, "Cash for 1st Wave:"); +// mvwprintz(w, 14, NUMALIGN(cash_per_wave), SELCOL(8), "%d", cash_per_wave); +// mvwprintz(w, 14, 28, c_ltgray, "The cash awarded for the first wave."); +// mvwprintz(w, 15, 2, c_ltgray, "Cash Increase:"); +// mvwprintz(w, 15, NUMALIGN(cash_increase), SELCOL(9), "%d", cash_increase); +// mvwprintz(w, 15, 28, c_ltgray, "The increase in the award each wave."); + +// mvwprintz(w, 17, 2, c_ltgray, "Enemy Selection:"); +// mvwprintz(w, 18, 2, TOGCOL(10, zombies), "Zombies"); +// mvwprintz(w, 18, 14, TOGCOL(11, specials), "Special Zombies"); +// mvwprintz(w, 18, 34, TOGCOL(12, spiders), "Spiders"); +// mvwprintz(w, 18, 46, TOGCOL(13, triffids), "Triffids"); +// mvwprintz(w, 18, 59, TOGCOL(14, robots), "Robots"); +// mvwprintz(w, 18, 70, TOGCOL(15, subspace), "Subspace"); + +// mvwprintz(w, 20, 2, c_ltgray, "Needs:"); +// mvwprintz(w, 21, 2, TOGCOL(16, hunger), "Food"); +// mvwprintz(w, 21, 16, TOGCOL(17, thirst), "Water"); +// mvwprintz(w, 21, 31, TOGCOL(18, sleep), "Sleep"); +// mvwprintz(w, 21, 46, TOGCOL(19, mercenaries), "Mercenaries"); +// wrefresh(w); +// } + +// std::string defense_style_name(defense_style style) +// { +// // 24 Characters Max! +// switch (style) { +// case DEFENSE_CUSTOM: return "Custom"; +// case DEFENSE_EASY: return "Easy"; +// case DEFENSE_MEDIUM: return "Medium"; +// case DEFENSE_HARD: return "Hard"; +// case DEFENSE_SHAUN: return "Shaun of the Dead"; +// case DEFENSE_DAWN: return "Dawn of the Dead"; +// case DEFENSE_SPIDERS: return "Eight-Legged Freaks"; +// case DEFENSE_TRIFFIDS: return "Day of the Triffids"; +// case DEFENSE_SKYNET: return "Skynet"; +// case DEFENSE_LOVECRAFT: return "The Call of Cthulhu"; +// default: return "Bug! Report this!"; +// } +// } + +// std::string defense_style_description(defense_style style) +// { +// // 51 Characters Max! +// switch (style) { +// case DEFENSE_CUSTOM: +// return "A custom game."; +// case DEFENSE_EASY: +// return "Easy monsters and lots of money."; +// case DEFENSE_MEDIUM: +// return "Harder monsters. You have to eat."; +// case DEFENSE_HARD: +// return "All monsters. You have to eat and drink."; +// case DEFENSE_SHAUN: +// return "Defend a bar against classic zombies. Easy and fun."; +// case DEFENSE_DAWN: +// return "Classic zombies. Slower and more realistic."; +// case DEFENSE_SPIDERS: +// return "Fast-paced spider-fighting fun!"; +// case DEFENSE_TRIFFIDS: +// return "Defend your mansion against the triffids."; +// case DEFENSE_SKYNET: +// return "The robots have decided that humans are the enemy!"; +// case DEFENSE_LOVECRAFT: +// return "Ward off legions of eldritch horrors."; +// default: +// return "What the heck is this I don't even know. A bug!"; +// } +// } + +// std::string defense_location_name(defense_location location) +// { +// switch (location) { +// case DEFLOC_NULL: return "Nowhere?! A bug!"; +// case DEFLOC_HOSPITAL: return "Hospital"; +// case DEFLOC_MALL: return "Megastore"; +// case DEFLOC_BAR: return "Bar"; +// case DEFLOC_MANSION: return "Mansion"; +// default: return "a ghost's house (bug)"; +// } +// } + +// std::string defense_location_description(defense_location location) +// { +// switch (location) { +// case DEFLOC_NULL: +// return "NULL Bug."; +// case DEFLOC_HOSPITAL: +// return "One entrance and many rooms. Some medical supplies."; +// case DEFLOC_MALL: +// return "A large building with various supplies."; +// case DEFLOC_BAR: +// return "A small building with plenty of alcohol."; +// case DEFLOC_MANSION: +// return "A large house with many rooms and."; +// default: +// return "Unknown data bug."; +// } +// } + +// void defense_game::caravan(game *g) +// { +// caravan_category tab = CARAVAN_MELEE; +// std::vector items[NUM_CARAVAN_CATEGORIES]; +// std::vector item_count[NUM_CARAVAN_CATEGORIES]; + +// // Init the items for each category +// for (int i = 0; i < NUM_CARAVAN_CATEGORIES; i++) { +// items[i] = caravan_items( caravan_category(i) ); +// for (int j = 0; j < items[i].size(); j++) { +// if (current_wave == 0 || !one_in(4)) +// item_count[i].push_back(0); // Init counts to 0 for each item +// else { // Remove the item +// items[i].erase( items[i].begin() + j); +// j--; +// } +// } +// } + +// int total_price = 0; + +// WINDOW *w = newwin(25, 80, 0, 0); + +// int offset = 0, item_selected = 0, category_selected = 0; + +// int current_window = 0; + +// draw_caravan_borders(w, current_window); +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); + +// bool done = false; +// bool cancel = false; +// while (!done) { + +// char ch = input(); +// switch (ch) { +// case '?': +// popup_top("\ +// CARAVAN:\n\ +// Start by selecting a category using your favorite up/down keys.\n\ +// Switch between category selection and item selecting by pressing Tab.\n\ +// Pick an item with the up/down keys, press + to buy 1 more, - to buy 1 less.\n\ +// Press Enter to buy everything in your cart, Esc to buy nothing."); +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, item_selected); +// draw_caravan_borders(w, current_window); +// break; + +// case 'j': +// if (current_window == 0) { // Categories +// category_selected++; +// if (category_selected == NUM_CARAVAN_CATEGORIES) +// category_selected = CARAVAN_CART; +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// offset = 0; +// item_selected = 0; +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, +// item_selected); +// draw_caravan_borders(w, current_window); +// } else if (items[category_selected].size() > 0) { // Items +// if (item_selected < items[category_selected].size() - 1) +// item_selected++; +// else { +// item_selected = 0; +// offset = 0; +// } +// if (item_selected > offset + 22) +// offset++; +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, +// item_selected); +// draw_caravan_borders(w, current_window); +// } +// break; + +// case 'k': +// if (current_window == 0) { // Categories +// if (category_selected == 0) +// category_selected = NUM_CARAVAN_CATEGORIES - 1; +// else +// category_selected--; +// if (category_selected == NUM_CARAVAN_CATEGORIES) +// category_selected = CARAVAN_CART; +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// offset = 0; +// item_selected = 0; +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, +// item_selected); +// draw_caravan_borders(w, current_window); +// } else if (items[category_selected].size() > 0) { // Items +// if (item_selected > 0) +// item_selected--; +// else { +// item_selected = items[category_selected].size() - 1; +// offset = item_selected - 22; +// if (offset < 0) +// offset = 0; +// } +// if (item_selected < offset) +// offset--; +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, +// item_selected); +// draw_caravan_borders(w, current_window); +// } +// break; + +// case '+': +// case 'l': +// if (current_window == 1 && items[category_selected].size() > 0) { +// item_count[category_selected][item_selected]++; +// itype_id tmp_itm = items[category_selected][item_selected]; +// total_price += caravan_price(g->u, g->itypes[tmp_itm]->price); +// if (category_selected == CARAVAN_CART) { // Find the item in its category +// for (int i = 1; i < NUM_CARAVAN_CATEGORIES; i++) { +// for (int j = 0; j < items[i].size(); j++) { +// if (items[i][j] == tmp_itm) +// item_count[i][j]++; +// } +// } +// } else { // Add / increase the item in the shopping cart +// bool found_item = false; +// for (int i = 0; i < items[0].size() && !found_item; i++) { +// if (items[0][i] == tmp_itm) { +// found_item = true; +// item_count[0][i]++; +// } +// } +// if (!found_item) { +// items[0].push_back(items[category_selected][item_selected]); +// item_count[0].push_back(1); +// } +// } +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, item_selected); +// draw_caravan_borders(w, current_window); +// } +// break; + +// case '-': +// case 'h': +// if (current_window == 1 && items[category_selected].size() > 0 && +// item_count[category_selected][item_selected] > 0) { +// item_count[category_selected][item_selected]--; +// itype_id tmp_itm = items[category_selected][item_selected]; +// total_price -= caravan_price(g->u, g->itypes[tmp_itm]->price); +// if (category_selected == CARAVAN_CART) { // Find the item in its category +// for (int i = 1; i < NUM_CARAVAN_CATEGORIES; i++) { +// for (int j = 0; j < items[i].size(); j++) { +// if (items[i][j] == tmp_itm) +// item_count[i][j]--; +// } +// } +// } else { // Decrease / remove the item in the shopping cart +// bool found_item = false; +// for (int i = 0; i < items[0].size() && !found_item; i++) { +// if (items[0][i] == tmp_itm) { +// found_item = true; +// item_count[0][i]--; +// if (item_count[0][i] == 0) { +// item_count[0].erase(item_count[0].begin() + i); +// items[0].erase(items[0].begin() + i); +// } +// } +// } +// } +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, item_selected); +// draw_caravan_borders(w, current_window); +// } +// break; + +// case '\t': +// current_window = (current_window + 1) % 2; +// draw_caravan_borders(w, current_window); +// break; + +// case KEY_ESCAPE: +// if (query_yn("Really buy nothing?")) { +// cancel = true; +// done = true; +// } else { +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, item_selected); +// draw_caravan_borders(w, current_window); +// } +// break; + +// case '\n': +// if (total_price > g->u.cash) +// popup("You can't afford those items!"); +// else if ((items[0].empty() && query_yn("Really buy nothing?")) || +// (!items[0].empty() && +// query_yn("Buy %d items, leaving you with $%d?", items[0].size(), +// g->u.cash - total_price))) +// done = true; +// if (!done) { // We canceled, so redraw everything +// draw_caravan_categories(w, category_selected, total_price, g->u.cash); +// draw_caravan_items(w, g, &(items[category_selected]), +// &(item_count[category_selected]), offset, item_selected); +// draw_caravan_borders(w, current_window); +// } +// break; +// } // switch (ch) + +// } // while (!done) + +// if (!cancel) { +// g->u.cash -= total_price; +// bool dropped_some = false; +// for (int i = 0; i < items[0].size(); i++) { +// item tmp(g->itypes[ items[0][i] ], g->turn); +// tmp = tmp.in_its_container(&(g->itypes)); +// for (int j = 0; j < item_count[0][i]; j++) { +// if (g->u.volume_carried() + tmp.volume() <= g->u.volume_capacity() && +// g->u.weight_carried() + tmp.weight() <= g->u.weight_capacity() && +// g->u.inv.size() < 52) +// g->u.i_add(tmp); +// else { // Could fit it in the inventory! +// dropped_some = true; +// g->m.add_item(g->u.posx, g->u.posy, tmp); +// } +// } +// } +// if (dropped_some) +// g->add_msg("You drop some items."); +// } +// } + +// std::string caravan_category_name(caravan_category cat) +// { +// switch (cat) { +// case CARAVAN_CART: return "Shopping Cart"; +// case CARAVAN_MELEE: return "Melee Weapons"; +// case CARAVAN_GUNS: return "Firearms & Ammo"; +// case CARAVAN_COMPONENTS: return "Crafting & Construction Components"; +// case CARAVAN_FOOD: return "Food & Drugs"; +// case CARAVAN_CLOTHES: return "Clothing & Armor"; +// case CARAVAN_TOOLS: return "Tools, Traps & Grenades"; +// } +// return "BUG"; +// } + +// std::vector caravan_items(caravan_category cat) +// { +// std::vector ret; +// switch (cat) { +// case CARAVAN_CART: +// return ret; + +// case CARAVAN_MELEE: +// setvector(ret, +// itm_hammer, itm_bat, itm_mace, itm_morningstar, itm_hammer_sledge, itm_hatchet, +// itm_knife_combat, itm_rapier, itm_machete, itm_katana, itm_spear_knife, +// itm_pike, itm_chainsaw_off, NULL); +// break; + +// case CARAVAN_GUNS: +// setvector(ret, +// itm_crossbow, itm_bolt_steel, itm_compbow, itm_arrow_cf, itm_marlin_9a, +// itm_22_lr, itm_hk_mp5, itm_9mm, itm_taurus_38, itm_38_special, itm_deagle_44, +// itm_44magnum, itm_m1911, itm_hk_ump45, itm_45_acp, itm_fn_p90, itm_57mm, +// itm_remington_870, itm_shot_00, itm_shot_slug, itm_browning_blr, itm_3006, +// itm_ak47, itm_762_m87, itm_m4a1, itm_556, itm_savage_111f, itm_hk_g3, +// itm_762_51, itm_hk_g80, itm_12mm, itm_plasma_rifle, itm_plasma, NULL); +// break; + +// case CARAVAN_COMPONENTS: +// setvector(ret, +// itm_rag, itm_fur, itm_leather, itm_superglue, itm_string_36, itm_chain, +// itm_processor, itm_RAM, itm_power_supply, itm_motor, itm_hose, itm_pot, +// itm_2x4, itm_battery, itm_nail, itm_gasoline, NULL); +// break; + +// case CARAVAN_FOOD: +// setvector(ret, +// itm_1st_aid, itm_water, itm_energy_drink, itm_whiskey, itm_can_beans, +// itm_mre_beef, itm_flour, itm_inhaler, itm_codeine, itm_oxycodone, itm_adderall, +// itm_cig, itm_meth, itm_royal_jelly, itm_mutagen, itm_purifier, NULL); +// break; + +// case CARAVAN_CLOTHES: +// setvector(ret, +// itm_backpack, itm_vest, itm_trenchcoat, itm_jacket_leather, itm_kevlar, +// itm_gloves_fingerless, itm_mask_filter, itm_mask_gas, itm_glasses_eye, +// itm_glasses_safety, itm_goggles_ski, itm_goggles_nv, itm_helmet_ball, +// itm_helmet_riot, NULL); +// break; + +// case CARAVAN_TOOLS: +// setvector(ret, +// itm_screwdriver, itm_wrench, itm_saw, itm_hacksaw, itm_lighter, itm_sewing_kit, +// itm_scissors, itm_extinguisher, itm_flashlight, itm_hotplate, +// itm_soldering_iron, itm_shovel, itm_jackhammer, itm_landmine, itm_teleporter, +// itm_grenade, itm_flashbang, itm_EMPbomb, itm_smokebomb, itm_bot_manhack, +// itm_bot_turret, itm_UPS_off, itm_mininuke, NULL); +// break; +// } + +// return ret; +// } + +// void draw_caravan_borders(WINDOW *w, int current_window) +// { +// // First, do the borders for the category window +// nc_color col = c_ltgray; +// if (current_window == 0) +// col = c_yellow; + +// mvwputch(w, 0, 0, col, LINE_OXXO); +// for (int i = 1; i <= 38; i++) { +// mvwputch(w, 0, i, col, LINE_OXOX); +// mvwputch(w, 11, i, col, LINE_OXOX); +// } +// for (int i = 1; i <= 10; i++) { +// mvwputch(w, i, 0, col, LINE_XOXO); +// mvwputch(w, i, 39, c_yellow, LINE_XOXO); // Shared border, always yellow +// } +// mvwputch(w, 11, 0, col, LINE_XXXO); + +// // These are shared with the items window, and so are always "on" +// mvwputch(w, 0, 39, c_yellow, LINE_OXXX); +// mvwputch(w, 11, 39, c_yellow, LINE_XOXX); + +// col = (current_window == 1 ? c_yellow : c_ltgray); +// // Next, draw the borders for the item description window--always "off" & gray +// for (int i = 12; i <= 23; i++) { +// mvwputch(w, i, 0, c_ltgray, LINE_XOXO); +// mvwputch(w, i, 39, col, LINE_XOXO); +// } +// for (int i = 1; i <= 38; i++) +// mvwputch(w, 24, i, c_ltgray, LINE_OXOX); + +// mvwputch(w, 24, 0, c_ltgray, LINE_XXOO); +// mvwputch(w, 24, 39, c_ltgray, LINE_XXOX); + +// // Finally, draw the item section borders +// for (int i = 40; i <= 78; i++) { +// mvwputch(w, 0, i, col, LINE_OXOX); +// mvwputch(w, 24, i, col, LINE_OXOX); +// } +// for (int i = 1; i <= 23; i++) +// mvwputch(w, i, 79, col, LINE_XOXO); + +// mvwputch(w, 24, 39, col, LINE_XXOX); +// mvwputch(w, 0, 79, col, LINE_OOXX); +// mvwputch(w, 24, 79, col, LINE_XOOX); + +// // Quick reminded about help. +// mvwprintz(w, 24, 2, c_red, "Press ? for help."); +// wrefresh(w); +// } + +// void draw_caravan_categories(WINDOW *w, int category_selected, int total_price, +// int cash) +// { +// // Clear the window +// for (int i = 1; i <= 10; i++) +// mvwprintz(w, i, 1, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +// mvwprintz(w, 1, 1, c_white, "Your Cash:%s%d", +// (cash >= 10000 ? " " : (cash >= 1000 ? " " : (cash >= 100 ? " " : +// (cash >= 10 ? " " : " ")))), cash); +// wprintz(w, c_ltgray, " -> "); +// wprintz(w, (total_price > cash ? c_red : c_green), "%d", cash - total_price); + +// for (int i = 0; i < NUM_CARAVAN_CATEGORIES; i++) +// mvwprintz(w, i + 3, 1, (i == category_selected ? h_white : c_white), +// caravan_category_name( caravan_category(i) ).c_str()); +// wrefresh(w); +// } + +// void draw_caravan_items(WINDOW *w, game *g, std::vector *items, +// std::vector *counts, int offset, +// int item_selected) +// { +// // Print the item info first. This is important, because it contains \n which +// // will corrupt the item list. + +// // Actually, clear the item info first. +// for (int i = 12; i <= 23; i++) +// mvwprintz(w, i, 1, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +// // THEN print it--if item_selected is valid +// if (item_selected < items->size()) { +// item tmp(g->itypes[ (*items)[item_selected] ], 0); // Dummy item to get info +// mvwprintz(w, 12, 0, c_white, tmp.info().c_str()); +// } +// // Next, clear the item list on the right +// for (int i = 1; i <= 23; i++) +// mvwprintz(w, i, 40, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +// // Finally, print the item list on the right +// for (int i = offset; i <= offset + 23 && i < items->size(); i++) { +// mvwprintz(w, i - offset + 1, 40, (item_selected == i ? h_white : c_white), +// g->itypes[ (*items)[i] ]->name.c_str()); +// wprintz(w, c_white, " x %s%d", ((*counts)[i] >= 10 ? "" : " "), (*counts)[i]); +// if ((*counts)[i] > 0) { +// int price = caravan_price(g->u, g->itypes[(*items)[i]]->price *(*counts)[i]); +// wprintz(w, (price > g->u.cash ? c_red : c_green), +// "($%s%d)", (price >= 100000 ? "" : (price >= 10000 ? " " : +// (price >= 1000 ? " " : (price >= 100 ? " " : +// (price >= 10 ? " " : " "))))), price); +// } +// } +// wrefresh(w); +// } + +// int caravan_price(player &u, int price) +// { +// if (u.sklevel[sk_barter] > 10) +// return int( double(price) * .5); +// return int( double(price) * (1.0 - double(u.sklevel[sk_barter]) * .05)); +// } + +// void defense_game::spawn_wave(game *g) +// { +// g->add_msg("********"); +// int diff = initial_difficulty + current_wave * wave_difficulty; +// bool themed_wave = one_in(SPECIAL_WAVE_CHANCE); // All a single monster type +// g->u.cash += cash_per_wave + (current_wave - 1) * cash_increase; +// std::vector valid; +// valid = pick_monster_wave(g); +// while (diff > 0) { +// // Clear out any monsters that exceed our remaining difficulty +// for (int i = 0; i < valid.size(); i++) { +// if (g->mtypes[valid[i]]->difficulty > diff) { +// valid.erase(valid.begin() + i); +// i--; +// } +// } +// if (valid.size() == 0) { +// g->add_msg("Welcome to Wave %d!", current_wave); +// g->add_msg("********"); +// return; +// } +// int rn = rng(0, valid.size() - 1); +// mtype *type = g->mtypes[valid[rn]]; +// if (themed_wave) { +// int num = diff / type->difficulty; +// if (num >= SPECIAL_WAVE_MIN) { +// // TODO: Do we want a special message here? +// for (int i = 0; i < num; i++) +// spawn_wave_monster(g, type); +// g->add_msg( special_wave_message(type->name).c_str() ); +// g->add_msg("********"); +// return; +// } else +// themed_wave = false; // No partially-themed waves +// } +// diff -= type->difficulty; +// spawn_wave_monster(g, type); +// } +// g->add_msg("Welcome to Wave %d!", current_wave); +// g->add_msg("********"); +// } + +// std::vector defense_game::pick_monster_wave(game *g) +// { +// std::vector valid; +// std::vector ret; + +// if (zombies || specials) { +// if (specials) +// valid.push_back(mcat_zombie); +// else +// valid.push_back(mcat_vanilla_zombie); +// } +// if (spiders) +// valid.push_back(mcat_spider); +// if (triffids) +// valid.push_back(mcat_triffid); +// if (robots) +// valid.push_back(mcat_robot); +// if (subspace) +// valid.push_back(mcat_nether); + +// if (valid.empty()) +// debugmsg("Couldn't find a valid monster group for defense!"); +// else +// ret = g->moncats[ valid[rng(0, valid.size() - 1)] ]; + +// return ret; +// } + +// void defense_game::spawn_wave_monster(game *g, mtype *type) +// { +// monster tmp(type); +// if (location == DEFLOC_HOSPITAL || location == DEFLOC_MALL) { +// tmp.posy = SEEY; // Always spawn to the north! +// tmp.posx = rng(SEEX * (MAPSIZE / 2), SEEX * (1 + MAPSIZE / 2)); +// } else if (one_in(2)) { +// tmp.spawn(rng(SEEX * (MAPSIZE / 2), SEEX * (1 + MAPSIZE / 2)), rng(1, SEEY)); +// if (one_in(2)) +// tmp.posy = SEEY * MAPSIZE - 1 - tmp.posy; +// } else { +// tmp.spawn(rng(1, SEEX), rng(SEEY * (MAPSIZE / 2), SEEY * (1 + MAPSIZE / 2))); +// if (one_in(2)) +// tmp.posx = SEEX * MAPSIZE - 1 - tmp.posx; +// } +// tmp.wandx = g->u.posx; +// tmp.wandy = g->u.posy; +// tmp.wandf = 150; +// // We wanna kill! +// tmp.anger = 100; +// tmp.morale = 100; +// g->z.push_back(tmp); +// } + +// std::string defense_game::special_wave_message(std::string name) +// { +// std::stringstream ret; +// ret << "Wave " << current_wave << ": "; +// name[0] += 'A' - 'a'; // Capitalize + +// for (int i = 2; i < name.size(); i++) { +// if (name[i - 1] == ' ') +// name[i] += 'A' - 'a'; +// } + +// switch (rng(1, 6)) { +// case 1: ret << name << " Invasion!"; break; +// case 2: ret << "Attack of the " << name << "s!"; break; +// case 3: ret << name << " Attack!"; break; +// case 4: ret << name << "s from Hell!"; break; +// case 5: ret << "Beware! " << name << "!"; break; +// case 6: ret << "The Day of the " << name << "!"; break; +// case 7: ret << name << " Party!"; break; +// case 8: ret << "Revenge of the " << name << "s!"; break; +// case 9: ret << "Rise of the " << name << "s!"; break; +// } + +// return ret.str(); +// } +// From 17cdd7b32944ae0c4db24fc9d71050e79a5cd1af Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Wed, 2 May 2012 23:13:50 -0400 Subject: [PATCH 11/51] Map damage --- mapgen.cpp | 18 ++++++++++++++++++ west.cpp | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/mapgen.cpp b/mapgen.cpp index 626994603a..d2ee7c0b18 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -6267,6 +6267,22 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, } while (!done); } } + if (terrain_type >= ot_house_north && terrain_type <= ot_mansion) { + for (int x = 0; x < SEEX * 2; x++) { + for (int y = 0; y <= SEEY * 2; y++) { + std::string junk; + if (one_in(100) && ter(x,y) != t_grass && ter(x,y) != t_dirt + && ter(x,y) <= t_dumpster) { + ter(x, y) = t_rubble; + } + if (one_in(3)) { + bash(x, y, rng(0, 150), junk); // Smash the fuck out of it + bash(x, y, rng(0, 150), junk); // Smash the fuck out of it + } + } + } + } + } void map::place_items(items_location loc, int chance, int x1, int y1, @@ -7607,6 +7623,8 @@ x: %d - %d, dx: %d cx: %d/%d", x1, x2, dx, cx_low, cx_hi, break; } + + } void mansion_room(map *m, int x1, int y1, int x2, int y2) diff --git a/west.cpp b/west.cpp index 1450b7f3de..ea7fc6d2f4 100644 --- a/west.cpp +++ b/west.cpp @@ -53,7 +53,7 @@ bool west_game::init (game *g) { // g->start_game(); - g->turn = MINUTES(STARTING_MINUTES + 300);// It's turn 0... + g->turn = MINUTES(STARTING_MINUTES);// It's turn 0... // run_mode = 1; // run_mode is on by default... // g->mostseen = 0; // ...and mostseen is 0, we haven't seen any monsters yet. @@ -83,7 +83,8 @@ bool west_game::init (game *g) // Init the starting map at this location. g->m.load(g, g->levx, g->levy); // Start us off somewhere in the shelter. - g->u.create(g, PLTYPE_CUSTOM); + if(!g->u.create(g, PLTYPE_CUSTOM)) + return false; g->u.posx = SEEX * int(MAPSIZE / 2) + 5; g->u.posy = SEEY * int(MAPSIZE / 2) + 5; g->u.str_cur = g->u.str_max; From fc97693710ce0fef0d8334058f095a8403f2dd97 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 3 May 2012 18:22:12 -0400 Subject: [PATCH 12/51] Morale changes, continued development on Journey to the West, terrain damage --- game.cpp | 14 ++++-- game.h | 2 +- gamemode.h | 2 + itypedef.cpp | 48 ++++++++++----------- mapgen.cpp | 9 ++-- morale.h | 6 ++- moraledata.h | 27 ++++++++++++ player.cpp | 33 +++++++++----- player.h | 2 +- west.cpp | 118 ++++++++++++++++++++++++++++----------------------- 10 files changed, 163 insertions(+), 98 deletions(-) diff --git a/game.cpp b/game.cpp index c07b7a730c..ecdead3676 100644 --- a/game.cpp +++ b/game.cpp @@ -636,7 +636,7 @@ bool game::do_turn() update_skills(); if (turn % 10 == 0) - u.update_morale(); + u.update_morale(this); return false; } @@ -728,7 +728,8 @@ void game::process_activity() if (reading->fun != 0) { std::stringstream morale_text; - u.add_morale(MORALE_BOOK, reading->fun * 5, reading->fun * 15, reading); + u.add_morale(MORALE_BOOK, reading->fun, + reading->fun > 0 ? reading->fun * 3 : 0, reading); } if (u.sklevel[reading->type] < reading->level) { @@ -746,6 +747,10 @@ void game::process_activity() (u.skexercise[reading->type] >= 100 ? 1 : 0) >= reading->level) add_msg("You can no longer learn from this %s.", reading->name.c_str()); } + else if (reading->fun > 0) + add_msg("You enjoy reading %s.", reading->name.c_str()); + else + add_msg("You %finish reading a section of %s.", reading->fun == 0 ? "" : " finally", reading->name.c_str()); break; case ACT_WAIT: @@ -1479,7 +1484,7 @@ bool game::load_master() return true; } -void game::load(std::string name) +bool game::load(std::string name) { std::ifstream fin; std::stringstream playerfile; @@ -1488,7 +1493,7 @@ void game::load(std::string name) // First, read in basic game state information. if (!fin.is_open()) { debugmsg("No save game exists!"); - return; + return false; } u = player(); u.name = name; @@ -1566,6 +1571,7 @@ void game::load(std::string name) load_master(); set_adjacent_overmaps(true); draw(); + return true; } void game::save() diff --git a/game.h b/game.h index 61ab02e7aa..1aecdeeb54 100644 --- a/game.h +++ b/game.h @@ -202,7 +202,7 @@ class game // Game-start procedures bool opening_screen();// Warn about screen size, then present the main menu bool load_master(); // Load the master data file, with factions &c - void load(std::string name); // Load a player-specific save file + bool load(std::string name); // Load a player-specific save file void start_game(); // Starts a new game void start_special_game(special_game_id gametype); // See gamemode.cpp diff --git a/gamemode.h b/gamemode.h index d95dc226f3..26c9614e37 100644 --- a/gamemode.h +++ b/gamemode.h @@ -185,6 +185,8 @@ struct west_game : public special_game { virtual bool init(game *g); virtual void per_turn(game * g); int distance_to_horde(game *g); + virtual void pre_action(game *g, action_id &act); + }; diff --git a/itypedef.cpp b/itypedef.cpp index f72f4e1932..c1eb005d4e 100644 --- a/itypedef.cpp +++ b/itypedef.cpp @@ -2470,45 +2470,45 @@ color,mat1,mat2,volume,wgt,melee_dam,0,to_hit,0,type,level,req,fun,intel,time)) BOOK("Playboy", 20, 30,c_pink, PAPER, MNULL, // VOL WGT DAM HIT TYPE LEV REQ FUN INT TIME - 1, 1, -3, 1, sk_null, 0, 0, 1, 0, 10, "\ + 1, 1, -3, 1, sk_null, 0, 0, 4, 0, 20, "\ You can read it for the articles. Or not."); BOOK("US Weekly", 40, 40,c_pink, PAPER, MNULL, - 1, 1, -3, 1, sk_speech, 1, 0, 1, 3, 8, "\ + 1, 1, -3, 1, sk_speech, 1, 0, 2, 3, 8, "\ Weekly news about a bunch of famous people who're all (un)dead now."); BOOK("TIME magazine", 35, 40,c_pink, PAPER, MNULL, - 1, 1, -3, 1, sk_null, 0, 0, 2, 7, 10, "\ + 1, 1, -3, 1, sk_null, 0, 0, 4, 7, 10, "\ Current events concerning a bunch of people who're all (un)dead now."); BOOK("Top Gear magazine", 40, 45,c_pink, PAPER, MNULL, - 1, 1, -3, 1, sk_mechanics, 1, 0, 1, 2, 8, "\ + 1, 1, -3, 1, sk_mechanics, 1, 0, 2, 2, 8, "\ Lots of articles about cars and mechanics. You might learn a little."); BOOK("Bon Appetit", 30, 45,c_pink, PAPER, MNULL, - 1, 1, -3, 1, sk_cooking, 1, 0, 1, 5, 8, "\ + 1, 1, -3, 1, sk_cooking, 1, 0, 2, 5, 8, "\ Exciting recipes and restaurant reviews. Full of handy tips about cooking."); BOOK("Guns n Ammo", 20, 48,c_pink, PAPER, MNULL, - 1, 1, -3, 1, sk_gun, 1, 0, 1, 2, 7, "\ + 1, 1, -3, 1, sk_gun, 1, 0, 2, 2, 7, "\ Reviews of firearms, and various useful tips about their use."); BOOK("romance novel", 30, 55,c_ltblue, PAPER, MNULL, - 4, 1, -2, 0, sk_null, 0, 0, 2, 4, 15, "\ + 4, 1, -2, 0, sk_null, 0, 0, 4, 4, 15, "\ Drama and mild smut."); BOOK("spy novel", 28, 55,c_ltblue, PAPER, MNULL, - 4, 1, -2, 0, sk_null, 0, 0, 3, 5, 18, "\ + 4, 1, -2, 0, sk_null, 0, 0, 6, 5, 18, "\ A tale of intrigue and espionage amongst Nazis, no, Commies, no, Iraqis!"); // NAME RAR PRC COLOR MAT1 MAT2 BOOK("scifi novel", 20, 55,c_ltblue, PAPER, MNULL, // VOL WGT DAM HIT TYPE LEV REQ FUN INT TIME - 3, 1, -3, 0, sk_null, 0, 0, 3, 6, 20, "\ + 3, 1, -3, 0, sk_null, 0, 0, 6, 6, 20, "\ Aliens, ray guns, and space ships."); BOOK("drama novel", 40, 55,c_ltblue, PAPER, MNULL, - 4, 1, -2, 0, sk_null, 0, 0, 4, 7, 25, "\ + 4, 1, -2, 0, sk_null, 0, 0, 8, 7, 25, "\ A real book for real adults."); BOOK("101 Wrestling Moves", 30, 180,c_green, PAPER, MNULL, @@ -2523,7 +2523,7 @@ A classic Soviet text on the art of attacking with a blade."); // NAME RAR PRC COLOR MAT1 MAT2 BOOK("Under the Hood", 35, 190,c_green, PAPER, MNULL, // VOL WGT DAM HIT TYPE LEV REQ FUN INT TIME - 3, 1, -3, 0, sk_mechanics, 3, 0, 0, 5, 18, "\ + 3, 1, -3, 0, sk_mechanics, 3, 0, -1, 5, 18, "\ An advanced mechanics manual, covering all sorts of topics."); BOOK("Self-Esteem for Dummies", 50, 160,c_green, PAPER, MNULL, @@ -2535,35 +2535,35 @@ BOOK("How to Succeed in Business",40,180,c_green, PAPER, MNULL, Useful if you want to get a good deal when purchasing goods."); BOOK("The Big Book of First Aid",40,200,c_green, PAPER, MNULL, - 5, 2, -2, 0, sk_firstaid, 3, 0, 0, 7, 20, "\ + 5, 2, -2, 0, sk_firstaid, 3, 0, -1, 7, 20, "\ It's big and heavy, but full of great information about first aid."); BOOK("How to Browse the Web", 20, 170,c_green, PAPER, MNULL, - 3, 1, -3, 0, sk_computer, 2, 0, 0, 5, 15, "\ + 3, 1, -3, 0, sk_computer, 2, 0, -1, 5, 15, "\ Very beginner-level information about computers."); // NAME RAR PRC COLOR MAT1 MAT2 BOOK("Cooking on a Budget", 35, 160,c_green, PAPER, MNULL, // VOL WGT DAM HIT TYPE LEV REQ FUN INT TIME - 4, 1, -2, 0, sk_cooking, 3, 0, 0, 4, 10, "\ + 4, 1, -2, 0, sk_cooking, 3, 0, -1, 4, 10, "\ A nice cook book that goes beyond recipes and into the chemistry of food."); BOOK("What's a Transistor?", 20, 200,c_green, PAPER, MNULL, - 3, 1, -3, 0, sk_electronics, 3, 0, 0, 7, 20, "\ + 3, 1, -3, 0, sk_electronics, 3, 0, -1, 7, 20, "\ A basic manual of electronics and circuit design."); BOOK("Sew What? Clothing!", 15, 190,c_green, PAPER, MNULL, - 3, 1, -3, 0, sk_tailor, 3, 0, 0, 4, 18, "\ + 3, 1, -3, 0, sk_tailor, 3, 0, -1, 4, 18, "\ A colorful book about tailoring."); BOOK("How to Trap Anything", 12, 240,c_green, PAPER, MNULL, - 2, 1, -3, 0, sk_traps, 4, 0, 0, 4, 20, "\ + 2, 1, -3, 0, sk_traps, 4, 0, -1, 4, 20, "\ A worn manual that describes how to set and disarm a wide variety of traps."); // NAME RAR PRC COLOR MAT1 MAT2 BOOK("Building for Beginners", 10, 220,c_green, PAPER, MNULL, // VOL WGT DAM HIT TYPE LEV REQ FUN INT TIME - 2, 1, -3, 0, sk_carpentry, 3, 0, 0, 5, 16, "\ + 2, 1, -3, 0, sk_carpentry, 3, 0, -1, 5, 16, "\ A large, paperback book detailing several beginner's projects in\n\ construction."); @@ -2572,30 +2572,30 @@ BOOK("Computer Science 301", 8, 500,c_blue, PAPER, MNULL, A college textbook on computer science."); BOOK("Advanced Electronics", 6, 520,c_blue, PAPER, MNULL, - 7, 5, 5, 1, sk_electronics, 5, 2, -1, 11, 35, "\ + 7, 5, 5, 1, sk_electronics, 5, 2, -2, 11, 35, "\ A college textbook on circuit design."); BOOK("Advanced Economics", 12, 480,c_blue, PAPER, MNULL, - 7, 4, 5, 1, sk_barter, 5, 3, -1, 9, 30, "\ + 7, 4, 5, 1, sk_barter, 5, 3, -2, 9, 30, "\ A college textbook on economics."); // NAME RAR PRC COLOR MAT1 MAT2 BOOK("Chemistry Textbook", 11, 495,c_blue, PAPER, MNULL, // VOL WGT DAM HIT TYPE LEV REQ FUN INT TIME - 8, 6, 5, 1, sk_cooking, 6, 3, -1, 12, 35, "\ + 8, 6, 5, 1, sk_cooking, 6, 3, -2, 12, 35, "\ A college textbook on chemistry."); BOOK("Engineering 301", 6, 550,c_blue, PAPER, MNULL, - 6, 3, 4, 1, sk_carpentry, 6, 3, -1, 8, 30, "\ + 6, 3, 4, 1, sk_carpentry, 6, 3, -2, 8, 30, "\ A textbook on civil engineering and construction."); BOOK("SICP", 3, 780,c_blue, PAPER, MNULL, - 6, 5, 6, 0, sk_computer, 8, 4, -1, 13, 50, "\ + 6, 5, 6, 0, sk_computer, 8, 4, -3, 13, 50, "\ A classic text, \"The Structure and Interpretation of Computer Programs.\"\n\ Written with examples in LISP, but applicable to any language."); BOOK("Robots for Fun & Profit", 1, 920,c_blue, PAPER, MNULL, - 8, 8, 8, 1, sk_electronics, 10, 5, -1, 14, 55, "\ + 8, 8, 8, 1, sk_electronics, 10, 5, -3, 14, 55, "\ A rare book on the design of robots, with lots of helpful step-by-step guides." ); diff --git a/mapgen.cpp b/mapgen.cpp index d2ee7c0b18..16c940688f 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -6272,12 +6272,15 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, for (int y = 0; y <= SEEY * 2; y++) { std::string junk; if (one_in(100) && ter(x,y) != t_grass && ter(x,y) != t_dirt - && ter(x,y) <= t_dumpster) { + && ter(x,y) <= t_dumpster + && !(ter(x,y) >= t_tree && ter(x,y) <= t_lava) + && ter(x,y) != t_pavement && ter(x,y) != t_pavement_y ) { ter(x, y) = t_rubble; } if (one_in(3)) { - bash(x, y, rng(0, 150), junk); // Smash the fuck out of it - bash(x, y, rng(0, 150), junk); // Smash the fuck out of it + bash(x, y, rng(0, 10)*rng(0, 10), junk); // Smash the fuck out of it + if (one_in(2)) + bash(x, y, rng(0, 10)*rng(0,10), junk); // Smash the fuck out of it } } } diff --git a/morale.h b/morale.h index 9c02111acc..b3ae406e31 100644 --- a/morale.h +++ b/morale.h @@ -39,10 +39,12 @@ struct morale_point morale_type type; itype* item_type; int bonus; - int halflife; + int last_dec; morale_point(morale_type T = MORALE_NULL, itype* I = NULL, int B = 0, int H = 15) : - type (T), item_type (I), bonus (B), halflife (H) {}; + type (T), item_type (I), bonus (B) { + last_dec = -1; + }; std::string name(std::string morale_data[]) { diff --git a/moraledata.h b/moraledata.h index c9716c8986..7e2dbed2ac 100644 --- a/moraledata.h +++ b/moraledata.h @@ -30,4 +30,31 @@ std::string morale_data[NUM_MORALE_TYPES] = { "Read %i", }; +int morale_halflives[NUM_MORALE_TYPES] = { + 0, // (bug) + 100, // Enjoyed %i + 75, // Music + 250, // Marloss + 75, // Good Feeling / Chem Imbalance + + 100, // Nicotine Craving + 100, // Caffeine Craving + 100, // Alcohol Craving + 100, // Opiate Craving + 100, // Speed Craving + 100, // Cocaine Craving + + 100, // Disliked %i + 100, // Ate Meat (vegetarian) + 75, // Wet + 75, // Bad Feeling / Chem Imbalance + 4000, // Killed Innocent + 10000, // Killed Friend + 2000, // Killed Mother + + 200, // Moodswing + 4000 // Book +}; + + #endif diff --git a/player.cpp b/player.cpp index 6a353c7395..bba78b8820 100644 --- a/player.cpp +++ b/player.cpp @@ -117,7 +117,7 @@ player& player::operator= (player rhs) inv.clear(); for (int i = 0; i < rhs.inv.size(); i++) inv.add_stack(rhs.inv.stack_at(i)); - + worn = rhs.worn; return (*this); } @@ -240,18 +240,29 @@ void player::reset(game *g) xp_pool = 800; } -void player::update_morale() +void player::update_morale(game * g) { for (int i = 0; i < morale.size(); i++) { - if (morale[i].bonus < 0) - morale[i].bonus++; - else if (morale[i].bonus > 0) - morale[i].bonus--; - - if (morale[i].bonus == 0) { - morale.erase(morale.begin() + i); - i--; - } + if (morale[i].last_dec == -1) { + morale[i].last_dec = g->turn; + } + else { + int cur_turn = g->turn; + int time_to_dec = morale_halflives[morale[i].type] * log(1 + 1./(abs(morale[i].bonus))) / log(2.0); + if (cur_turn - morale[i].last_dec >= time_to_dec) { + morale[i].bonus -= sgn(morale[i].bonus); + morale[i].last_dec = g->turn; + // if (morale[i].bonus < 0) + // morale[i].bonus++; + // else if (morale[i].bonus > 0) + // morale[i].bonus--; + + if (morale[i].bonus == 0) { + morale.erase(morale.begin() + i); + i--; + } + } + } } } diff --git a/player.h b/player.h index c543614e28..f211578167 100644 --- a/player.h +++ b/player.h @@ -54,7 +54,7 @@ class player { void disp_status(WINDOW* w, game *g = NULL);// On-screen data void reset(game *g = NULL);// Resets movement points, stats, applies effects - void update_morale(); // Ticks down morale counters and removes them + void update_morale(game *g = NULL); // Ticks down morale counters and removes them int current_speed(game *g = NULL); // Number of movement points we get a turn int run_cost(int base_cost); // Adjust base_cost int swim_speed(); // Our speed when swimming diff --git a/west.cpp b/west.cpp index ea7fc6d2f4..c3998e7e00 100644 --- a/west.cpp +++ b/west.cpp @@ -52,53 +52,66 @@ bool west_game::init (game *g) { + if(!g->u.create(g, PLTYPE_CUSTOM)) + return false; + + if (g->load(g->u.name)) { + if(g->u.cash == 0) + horde_location = g->global_location().x - 10; + else + horde_location = g->u.cash; + + g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); + + return true; + } + // g->start_game(); - g->turn = MINUTES(STARTING_MINUTES);// It's turn 0... - // run_mode = 1; // run_mode is on by default... - // g->mostseen = 0; // ...and mostseen is 0, we haven't seen any monsters yet. - -// Init some factions. - // if (!g->load_master()) // Master data record contains factions. - // g->create_factions(); - g->cur_om = overmap(g, 0, 0, 0); // We start in the (0,0,0) overmap. -// Find a random house on the map, and set us there. - g->cur_om.first_house(g->levx, g->levy); - g->levx -= int(int(MAPSIZE / 2) / 2); - g->levy -= int(int(MAPSIZE / 2) / 2); - g->levz = 0; -// Start the overmap out with none of it seen by the player... - for (int i = 0; i < OMAPX; i++) { - for (int j = 0; j < OMAPX; j++) - g->cur_om.seen(i, j) = false; - } -// ...except for our immediate neighborhood. - for (int i = -15; i <= 15; i++) { - for (int j = -15; j <= 15; j++) - g->cur_om.seen(g->levx + i, g->levy + j) = true; - } -// Convert the overmap coordinates to submap coordinates - g->levx = g->levx * 2 - 1; - g->levy = g->levy * 2 - 1; - g->set_adjacent_overmaps(true); -// Init the starting map at this location. - g->m.load(g, g->levx, g->levy); -// Start us off somewhere in the shelter. - if(!g->u.create(g, PLTYPE_CUSTOM)) - return false; - g->u.posx = SEEX * int(MAPSIZE / 2) + 5; - g->u.posy = SEEY * int(MAPSIZE / 2) + 5; - g->u.str_cur = g->u.str_max; - g->u.per_cur = g->u.per_max; - g->u.int_cur = g->u.int_max; - g->u.dex_cur = g->u.dex_max; - // g->nextspawn = int(g->turn); - g->temperature = 65; // Springtime-appropriate? - - g->u.normalize(g); - - horde_location = g->global_location().x - 10; - - return true; + g->turn = MINUTES(STARTING_MINUTES + 60);// It's turn 0... + // run_mode = 1; // run_mode is on by default... + // g->mostseen = 0; // ...and mostseen is 0, we haven't seen any monsters yet. + + // Init some factions. + // if (!g->load_master()) // Master data record contains factions. + // g->create_factions(); + g->cur_om = overmap(g, 0, 0, 0); // We start in the (0,0,0) overmap. + // Find a random house on the map, and set us there. + g->cur_om.first_house(g->levx, g->levy); + g->levx -= int(int(MAPSIZE / 2) / 2); + g->levy -= int(int(MAPSIZE / 2) / 2); + g->levz = 0; + // Start the overmap out with none of it seen by the player... + for (int i = 0; i < OMAPX; i++) { + for (int j = 0; j < OMAPX; j++) + g->cur_om.seen(i, j) = false; + } + // ...except for our immediate neighborhood. + for (int i = -15; i <= 15; i++) { + for (int j = -15; j <= 15; j++) + g->cur_om.seen(g->levx + i, g->levy + j) = true; + } + // Convert the overmap coordinates to submap coordinates + g->levx = g->levx * 2 - 1; + g->levy = g->levy * 2 - 1; + g->set_adjacent_overmaps(true); + // Init the starting map at this location. + g->m.load(g, g->levx, g->levy); + // Start us off somewhere in the shelter. + g->u.posx = SEEX * int(MAPSIZE / 2) + 5; + g->u.posy = SEEY * int(MAPSIZE / 2) + 5; + g->u.str_cur = g->u.str_max; + g->u.per_cur = g->u.per_max; + g->u.int_cur = g->u.int_max; + g->u.dex_cur = g->u.dex_max; + // g->nextspawn = int(g->turn); + g->temperature = 65; // Springtime-appropriate? + + g->u.normalize(g); + g->u.weapon = item(g->itypes[itm_baton], 0, 'a' + g->u.worn.size()); + + horde_location = g->global_location().x - 10; + + return true; } int west_game::distance_to_horde(game *g) { @@ -184,16 +197,17 @@ void west_game::per_turn(game *g) // } // } -// void defense_game::pre_action(game *g, action_id &act) -// { +void west_game::pre_action(game *g, action_id &act) +{ + if (act == ACTION_SAVE) { + g->u.cash = horde_location; + } +} + // if (act == ACTION_SLEEP && !sleep) { // g->add_msg("You don't need to sleep!"); // act = ACTION_NULL; // } -// if (act == ACTION_SAVE) { -// g->add_msg("You cannot save in defense mode!"); -// act = ACTION_NULL; -// } // // Big ugly block for movement // if ((act == ACTION_MOVE_N && g->u.posy == SEEX * int(MAPSIZE / 2) && From 9d92187d39a7c709b7a31a286d4abda6c6c488cb Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 3 May 2012 18:25:24 -0400 Subject: [PATCH 13/51] Catacurses fix --- catacurse.cpp | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/catacurse.cpp b/catacurse.cpp index 25c4855123..ecfac42dbf 100644 --- a/catacurse.cpp +++ b/catacurse.cpp @@ -110,16 +110,16 @@ LRESULT CALLBACK ProcessMessages(HWND__ *hWnd,unsigned int Msg, case WM_KEYDOWN: //Here we handle non-character input switch (wParam){ case VK_LEFT: - lastchar = 52; + lastchar = KEY_LEFT; break; case VK_RIGHT: - lastchar = 54; + lastchar = KEY_RIGHT; break; case VK_UP: - lastchar = 56; + lastchar = KEY_UP; break; case VK_DOWN: - lastchar = 50; + lastchar = KEY_DOWN; break; default: break; @@ -285,8 +285,8 @@ fin.open("data\\FONTDATA"); halfheight=fontheight / 2; WindowWidth=80*fontwidth; WindowHeight=25*fontheight; - WindowX=(GetSystemMetrics(SM_CXSCREEN) / 2)-WindowWidth; //center this - WindowY=(GetSystemMetrics(SM_CYSCREEN) / 2)-WindowHeight; //sucker + WindowX=(GetSystemMetrics(SM_CXSCREEN) / 2)-WindowWidth/2; //center this + WindowY=(GetSystemMetrics(SM_CYSCREEN) / 2)-WindowHeight/2; //sucker WinCreate(); //Create the actual window, register it, etc CheckMessages(); //Let the message queue handle setting up the window WindowDC = GetDC(WindowHandle); @@ -378,17 +378,20 @@ int delwin(WINDOW *win) return 1; }; -inline void newline(WINDOW *win){ -if (win->cursory < win->height - 1) - win->cursory++; -win->cursorx=0; +inline int newline(WINDOW *win){ + if (win->cursory < win->height - 1){ + win->cursory++; + win->cursorx=0; + return 1; + } +return 0; }; inline void addedchar(WINDOW *win){ -win->cursorx++; -win->line[win->cursory].touched=true; -if (win->cursorx > win->width) -newline(win); + win->cursorx++; + win->line[win->cursory].touched=true; + if (win->cursorx > win->width) + newline(win); }; @@ -484,18 +487,19 @@ inline int printstring(WINDOW *win, char *fmt) int size = strlen(fmt); int j; for (j=0; jcursorx <= win->width - 1 && win->cursory <= win->height - 1) { win->line[win->cursory].chars[win->cursorx]=fmt[j]; win->line[win->cursory].FG[win->cursorx]=win->FG; win->line[win->cursory].BG[win->cursorx]=win->BG; win->line[win->cursory].touched=true; addedchar(win); - } - } else if (win->cursory >= win->height - 1) - return 0; - else - newline(win); // if found, make sure to move down a line + } else + return 0; //if we try and write anything outside the window, abort completely +} else // if the character is a newline, make sure to move down a line + if (newline(win)==0){ + return 0; + } } win->draw=true; return 1; From 20870dc32a187b175661a1ae8aeb16da04b88cef Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 3 May 2012 18:29:02 -0400 Subject: [PATCH 14/51] Fixed windows compile for my machine --- Makefile.Windows | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile.Windows b/Makefile.Windows index dc8e0ea1c7..a6704af7ad 100644 --- a/Makefile.Windows +++ b/Makefile.Windows @@ -11,12 +11,13 @@ DDIR = .deps TARGET = cataclysm.exe -CXX = i486-mingw32-g++ +CXX = /usr/local/mingw/bin/i386-mingw32-g++ -LINKER = i486-mingw32-ld +LINKER = /usr/local/mingw/bin/i386-mingw32-ld LINKERFLAGS = -Wl,-stack,12000000,-subsystem,windows -CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) +CFLAGS = -O1 +#CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) LDFLAGS = -static -lgdi32 From 46b3496aad7791b8e3da45f61106914b3af2c9e3 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Fri, 11 May 2012 20:24:27 -0400 Subject: [PATCH 15/51] Give less direct information about distant hordes --- Makefile | 2 +- game.cpp | 3 +- monster.cpp | 2 +- player.cpp | 2 ++ west.cpp | 80 ++++++++++++++++++++++++++++++----------------------- 5 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 7ee85c3844..59bfe6f156 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ TARGET = cataclysm OS = $(shell uname -o) CXX = g++ -CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) +CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) -std=c++0x ifeq ($(OS), Msys) LDFLAGS = -static -lpdcurses diff --git a/game.cpp b/game.cpp index 34926c4b87..75e7ec6b5e 100644 --- a/game.cpp +++ b/game.cpp @@ -8,6 +8,7 @@ #include "weather_data.h" #include #include +#include #include #include #include @@ -1637,7 +1638,7 @@ void game::decrease_nextinv() else if (nextinv == 'A') nextinv = 'z'; else - nextinv++; + nextinv--; } void game::add_msg(const char* msg, ...) diff --git a/monster.cpp b/monster.cpp index 99f7894ed5..4767382eaa 100644 --- a/monster.cpp +++ b/monster.cpp @@ -176,7 +176,7 @@ void monster::print_info(game *g, WINDOW* w) damage_info = "It is heavily injured"; col = c_yellow; } else if (hp >= type->hp * .1) { - damage_info = "It is severly injured"; + damage_info = "It is severely injured"; col = c_ltred; } else { damage_info = "it is nearly dead"; diff --git a/player.cpp b/player.cpp index bba78b8820..8b430737c3 100644 --- a/player.cpp +++ b/player.cpp @@ -118,6 +118,7 @@ player& player::operator= (player rhs) for (int i = 0; i < rhs.inv.size(); i++) inv.add_stack(rhs.inv.stack_at(i)); worn = rhs.worn; + weapon = rhs.weapon; return (*this); } @@ -1685,6 +1686,7 @@ int player::clairvoyance() { if (has_artifact_with(AEP_CLAIRVOYANCE)) return 3; + return 0; } bool player::has_two_arms() diff --git a/west.cpp b/west.cpp index c3998e7e00..c65dd6be87 100644 --- a/west.cpp +++ b/west.cpp @@ -61,7 +61,8 @@ bool west_game::init (game *g) else horde_location = g->u.cash; - g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); + // g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); + popup_top("The horde comes."); return true; } @@ -154,48 +155,59 @@ int west_game::distance_to_horde(game *g) { void west_game::per_turn(game *g) { + auto spam_zombies = [&] (int n = 200, bool fast_only = false) { + if (n <= 0) return; + g->cancel_activity(); + g->u.rem_disease(DI_SLEEP); + g->u.rem_disease(DI_LYING_DOWN); + int monx, mony; + for (int i = 0; i < n; i++) { + mon_id type = fast_only ? mon_zombie_fast : g->valid_monster_from(g->moncats[mcat_zombie]); + if (type != mon_null) { + monx = rng(0, SEEX * MAPSIZE - 1); + mony = rng(0, SEEY * MAPSIZE - 1); + monster zom = monster(g->mtypes[type]); + zom.spawn(monx, mony); + zom.wandx = g->u.posx; + zom.wandy = g->u.posy; + zom.moves = -100; + g->z.push_back(zom); + } + } + }; + if (int(g->turn) % 300 == 0) { horde_location++; int dh = distance_to_horde(g); - int monx, mony; if (dh <= 0) { - for (int i = 0; i < 200; i++) { - mon_id type = g->valid_monster_from(g->moncats[mcat_zombie]); - if (type != mon_null) { - monx = rng(0, SEEX * MAPSIZE - 1); - mony = rng(0, SEEY * MAPSIZE - 1); - monster zom = monster(g->mtypes[type]); - zom.spawn(monx, mony); - zom.wandx = g->u.posx; - zom.wandy = g->u.posy; - g->z.push_back(zom); - } - } - g->add_msg("The horde is upon you!!"); + popup("The horde is upon you!!"); + spam_zombies(); } - else if (dh < 20) { - g->add_msg("The horde is only %d map squares away!", distance_to_horde(g)); + else if (dh <= 5) { + popup("The horde is starting to catch up with you!\nThe main body of the horde is only %d map squares away!", dh); + int badness = 5 - dh; + spam_zombies(dice(badness,4), true); } - else { - g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); + else if (dh < 15) { + popup("The horde comes! They are only %d map squares away.", dh); + g->cancel_activity_query("The horde comes!"); + g->u.rem_disease(DI_SLEEP); + g->u.rem_disease(DI_LYING_DOWN); + } + else if (dh < 30) { + if(g->u.has_disease(DI_SLEEP) || g->u.has_disease(DI_LYING_DOWN)) + g->add_msg("The distant horde approaches."); + else + popup("The distant horde approaches."); } + else { + if(g->u.has_disease(DI_SLEEP) || g->u.has_disease(DI_LYING_DOWN)) + g->add_msg("In the far distance, the horde moves."); + else + popup("In the far distance, the horde moves ever forward."); + } } } -// if (!thirst) -// g->u.thirst = 0; -// if (!hunger) -// g->u.hunger = 0; -// if (!sleep) -// g->u.fatigue = 0; -// if (int(g->turn) % (time_between_waves * 10) == 0) { -// current_wave++; -// if (current_wave > 1 && current_wave % waves_between_caravans == 0) { -// popup("A caravan approaches! Press spacebar..."); -// caravan(g); -// } -// spawn_wave(g); -// } -// } void west_game::pre_action(game *g, action_id &act) { From 6cffc53386558e8f54a123cce14e548b20c746b8 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 12 May 2012 11:27:17 -0400 Subject: [PATCH 16/51] Now easier to increase skills that have rusted; bug fixes --- construction.cpp | 5 ++--- game.cpp | 3 +++ player.cpp | 22 +++++++++++++++++----- player.h | 1 + wish.cpp | 12 ++++++------ 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/construction.cpp b/construction.cpp index 1e06313c98..80b95d24e1 100644 --- a/construction.cpp +++ b/construction.cpp @@ -443,9 +443,8 @@ void game::complete_construction() std::vector player_use; std::vector map_use; - u.practice(sk_carpentry, built->difficulty * 10); - if (built->difficulty < 1) - u.practice(sk_carpentry, 10); + u.practice(sk_carpentry, std::max(built->difficulty*10, 10)); + for (int i = 0; i < 3; i++) { if (!stage.components[i].empty()) consume_items(this, stage.components[i]); diff --git a/game.cpp b/game.cpp index 75e7ec6b5e..49c3b465dc 100644 --- a/game.cpp +++ b/game.cpp @@ -673,6 +673,9 @@ void game::update_skills() skill_name(skill(i)).c_str() ,u.sklevel[i]); u.skexercise[i] = 0; } + if (u.sklevel[i]*100 + u.skexercise[i] > u.maxskill[i]) { + u.maxskill[i] = u.sklevel[i]*100 + u.skexercise[i]; + } } } diff --git a/player.cpp b/player.cpp index 8b430737c3..4f4ae99b77 100644 --- a/player.cpp +++ b/player.cpp @@ -56,6 +56,7 @@ player::player() for (int i = 0; i < num_skill_types; i++) { sklevel[i] = 0; skexercise[i] = 0; + maxskill[i] = 0; } for (int i = 0; i < PF_MAX2; i++) my_traits[i] = false; @@ -106,6 +107,7 @@ player& player::operator= (player rhs) for (int i = 0; i < num_skill_types; i++) { sklevel[i] = rhs.sklevel[i]; skexercise[i] = rhs.skexercise[i]; + maxskill[i] = rhs.maxskill[i]; } for (int i = 0; i < PF_MAX2; i++) my_traits[i] = rhs.my_traits[i]; @@ -454,7 +456,7 @@ void player::load_info(game *g, std::string data) for (int i = 0; i < num_hp_parts; i++) dump >> hp_cur[i] >> hp_max[i]; for (int i = 0; i < num_skill_types; i++) - dump >> sklevel[i] >> skexercise[i]; + dump >> sklevel[i] >> skexercise[i] >> maxskill[i]; int numill; int typetmp; @@ -522,7 +524,7 @@ std::string player::save_info() for (int i = 0; i < num_hp_parts; i++) dump << hp_cur[i] << " " << hp_max[i] << " "; for (int i = 0; i < num_skill_types; i++) - dump << int(sklevel[i]) << " " << skexercise[i] << " "; + dump << int(sklevel[i]) << " " << skexercise[i] << " " << int(maxskill[i]) << " "; dump << illness.size() << " "; for (int i = 0; i < illness.size(); i++) @@ -1815,6 +1817,14 @@ int player::comprehension_percent(skill s, bool real_life) else if (!real_life && intel > 8) percent += 125 - 1000 / intel; + if (real_life && skexercise[s] < 0) + percent *= 1.5; + + if (real_life) { + int rust = std::max(0u, maxskill[s] - sklevel[s]*100 - skexercise[s]); + percent *= pow(2.0, rust / 100.0); + } + if (has_trait(PF_FASTLEARNER)) percent += 50.; return (int)(percent); @@ -4299,6 +4309,9 @@ bool player::wearing_something_on(body_part bp) void player::practice(skill s, int amount) { + int rust = (std::max)(0u, maxskill[s] - sklevel[s]*100 - skexercise[s]); + amount *= pow(2.0, rust / 100.0); + skill savant = sk_null; int savant_level = 0, savant_exercise = 0; if (has_trait(PF_SAVANT)) { @@ -4316,9 +4329,8 @@ void player::practice(skill s, int amount) } while (amount > 0 && xp_pool >= (1 + sklevel[s])) { amount -= sklevel[s] + 1; - if ((savant == sk_null || savant == s || !one_in(2)) && - rng(0, 100) < comprehension_percent(s)) { - xp_pool -= (1 + sklevel[s]); + if ((savant == sk_null || savant == s || !one_in(2)) && rng(0, 100) < comprehension_percent(s)) { + xp_pool -= (1 + sklevel[s])/(1 + rust/100.0); skexercise[s]++; } } diff --git a/player.h b/player.h index f211578167..654c83d2a2 100644 --- a/player.h +++ b/player.h @@ -242,6 +242,7 @@ class player { int xp_pool; int sklevel[num_skill_types]; int skexercise[num_skill_types]; + unsigned int maxskill[num_skill_types]; //100*level + exercise bool inv_sorted; //std::vector inv; diff --git a/wish.cpp b/wish.cpp index 865020ba5f..b46f97403e 100644 --- a/wish.cpp +++ b/wish.cpp @@ -66,19 +66,19 @@ void game::wish() for (int i = 0; i < itypes.size(); i++) { if (itypes[i]->name.find(pattern) != std::string::npos) { shift = i; - a = 0; result_selected = 0; - if (shift + 23 > itypes.size()) { - a = shift + 23 - itypes.size(); - shift = itypes.size() - 23; - } found = true; search_results.push_back(i); } } if (search_results.size() > 0) { shift = search_results[0]; - a = 0; + if (shift + 23 > itypes.size()) { + a = shift + 23 - itypes.size(); + shift = itypes.size() - 23; + } + else + a = 0; } } From 44fea16b8b72cc776f40702674e84a6cda3ced76 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 12 May 2012 11:41:58 -0400 Subject: [PATCH 17/51] change skill comprehension effect of rust to be more conservative --- player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.cpp b/player.cpp index 4f4ae99b77..7814689dbe 100644 --- a/player.cpp +++ b/player.cpp @@ -1822,7 +1822,7 @@ int player::comprehension_percent(skill s, bool real_life) if (real_life) { int rust = std::max(0u, maxskill[s] - sklevel[s]*100 - skexercise[s]); - percent *= pow(2.0, rust / 100.0); + percent += 10.0 * (rust / 100.0); } if (has_trait(PF_FASTLEARNER)) From 4afbdef3ee2162cee27a0725f9c3e9924b368016 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 12 May 2012 11:54:44 -0400 Subject: [PATCH 18/51] Windows makefile fix --- Makefile.Windows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.Windows b/Makefile.Windows index a6704af7ad..1c381cb833 100644 --- a/Makefile.Windows +++ b/Makefile.Windows @@ -16,7 +16,7 @@ CXX = /usr/local/mingw/bin/i386-mingw32-g++ LINKER = /usr/local/mingw/bin/i386-mingw32-ld LINKERFLAGS = -Wl,-stack,12000000,-subsystem,windows -CFLAGS = -O1 +CFLAGS = -O1 -std=c++0x #CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) LDFLAGS = -static -lgdi32 From 527bf904d1af2e1112bfc97d01c461462c1b1c21 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 12 May 2012 11:59:53 -0400 Subject: [PATCH 19/51] Even working-er Makefile.Windows --- Makefile | 2 +- Makefile.Windows | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 59bfe6f156..584a0d1863 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ TARGET = cataclysm OS = $(shell uname -o) CXX = g++ -CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) -std=c++0x +CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) -std=gnu++0x ifeq ($(OS), Msys) LDFLAGS = -static -lpdcurses diff --git a/Makefile.Windows b/Makefile.Windows index 1c381cb833..7eb993df05 100644 --- a/Makefile.Windows +++ b/Makefile.Windows @@ -16,7 +16,7 @@ CXX = /usr/local/mingw/bin/i386-mingw32-g++ LINKER = /usr/local/mingw/bin/i386-mingw32-ld LINKERFLAGS = -Wl,-stack,12000000,-subsystem,windows -CFLAGS = -O1 -std=c++0x +CFLAGS = -O1 -std=gnu++0x #CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) LDFLAGS = -static -lgdi32 From 4ae5d40ff48ff6cf993b6df06ed5810ff318029c Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Mon, 14 May 2012 12:48:25 -0400 Subject: [PATCH 20/51] Improve warnings --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7ee85c3844..80ccb2fe0c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # WARNINGS will spam hundreds of warnings, mostly safe, if turned on # DEBUG is best turned on if you plan to debug in gdb -- please do! # PROFILE is for use with gprof or a similar program -- don't bother generally -#WARNINGS = -Wall +WARNINGS = -Wall -Wextra -Wno-switch -Wno-sign-compare -Wno-missing-braces -Wno-unused-parameter -Wno-char-subscripts DEBUG = -g #PROFILE = -pg From 5b8035217dd1dcd48c7ad9fba4160acdbea860bb Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Mon, 14 May 2012 20:28:41 -0400 Subject: [PATCH 21/51] Fix bash crashes. --- mapgen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapgen.cpp b/mapgen.cpp index 16c940688f..24feb9bcf2 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -6269,7 +6269,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, } if (terrain_type >= ot_house_north && terrain_type <= ot_mansion) { for (int x = 0; x < SEEX * 2; x++) { - for (int y = 0; y <= SEEY * 2; y++) { + for (int y = 0; y < SEEY * 2; y++) { std::string junk; if (one_in(100) && ter(x,y) != t_grass && ter(x,y) != t_dirt && ter(x,y) <= t_dumpster From 8c17a3b11a1d5276723644666022580f9a3ba45e Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 17 May 2012 09:57:09 -0400 Subject: [PATCH 22/51] A few bugs I've already reported; makefile with more debug symbols --- Makefile | 2 +- calendar.cpp | 2 +- mapgen.cpp | 2 +- player.cpp | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 80ccb2fe0c..e19c318d12 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # DEBUG is best turned on if you plan to debug in gdb -- please do! # PROFILE is for use with gprof or a similar program -- don't bother generally WARNINGS = -Wall -Wextra -Wno-switch -Wno-sign-compare -Wno-missing-braces -Wno-unused-parameter -Wno-char-subscripts -DEBUG = -g +DEBUG = -ggdb -g3 #PROFILE = -pg ODIR = obj diff --git a/calendar.cpp b/calendar.cpp index aa28eda27c..122ffcacbb 100644 --- a/calendar.cpp +++ b/calendar.cpp @@ -218,7 +218,7 @@ moon_phase calendar::moon() { int phase = day / (DAYS_IN_SEASON / 4); //phase %= 4; Redundant? - if (phase = 3) + if (phase == 3) return MOON_HALF; else return moon_phase(phase); diff --git a/mapgen.cpp b/mapgen.cpp index 0e12893790..6159114b4a 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -2927,7 +2927,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, std::vector next; for (int nx = x - 1; nx <= x + 1; nx++ ) { for (int ny = y; ny <= y + 1; ny++) { - if (ter(nx, ny) == t_rock_floor); + if (ter(nx, ny) == t_rock_floor) next.push_back( point(nx, ny) ); } } diff --git a/player.cpp b/player.cpp index 4e4abd470d..c3dee234cf 100644 --- a/player.cpp +++ b/player.cpp @@ -1655,6 +1655,7 @@ int player::clairvoyance() { if (has_artifact_with(AEP_CLAIRVOYANCE)) return 3; + return 0; } bool player::has_two_arms() From c7fea86f54f7ada2923204e8a4213c6cb01c2868 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 26 May 2012 13:17:31 -0400 Subject: [PATCH 23/51] Fix mansion generation bugs --- mapgen.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mapgen.cpp b/mapgen.cpp index 4529db6a65..947afeba10 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -5047,7 +5047,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, if (one_in(2)) ter(rng(cw + 2, rw - 1), y) = t_door_c; else - ter(rng(y + 2, SEEY * 2 - 3), cw) = t_door_c; + ter(cw, rng(y + 2, SEEY * 2 - 3)) = t_door_c; if (t_west == ot_mansion_entrance || t_west == ot_mansion) line(this, t_floor, 0, SEEY - 1, 0, SEEY); @@ -7396,7 +7396,7 @@ x: %d - %d, dx: %d cx: %d/%d", x1, x2, dx, cx_low, cx_hi, y1, y2, dy, cy_low, cy_hi); */ bool walled_west = (x1 == 0), walled_north = (y1 == 0), - walled_east = (x2 == SEEX * 2 - 1), walled_south = (y2 == SEEY * 2 - 1); + walled_east = (x2 == SEEX * 2 - 1), walled_south = (y2 == SEEY * 2 - 2); switch (type) { @@ -7418,6 +7418,10 @@ x: %d - %d, dx: %d cx: %d/%d", x1, x2, dx, cx_low, cx_hi, if (m->ter(i, y2 + 1) != t_door_c) m->ter(i, y2) = t_shrub; } + if (walled_south && x1 <= SEEX && SEEX <= x2) { + m->ter(SEEX - 1, y2) = grass_or_dirt(); + m->ter(SEEX, y2) = grass_or_dirt(); + } } break; From c856ceca84c6caed283ca6b5020aca7672da8d7d Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 31 May 2012 14:04:29 -0400 Subject: [PATCH 24/51] Stop query for vehicle work (instead of automatically stopping for all interruptions). --- game.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/game.cpp b/game.cpp index 49c3b465dc..6e0c238caf 100644 --- a/game.cpp +++ b/game.cpp @@ -813,6 +813,10 @@ void game::cancel_activity_query(const char* message, ...) if (query_yn("%s Stop construction?", s.c_str())) u.activity.type = ACT_NULL; break; + case ACT_VEHICLE: + if (query_yn("%s Stop construction?", s.c_str())) + u.activity.type = ACT_NULL; + break; default: u.activity.type = ACT_NULL; } From 5f461a44e02ec4779540ed735bbf9744f4d011cb Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Wed, 6 Jun 2012 09:42:11 -0400 Subject: [PATCH 25/51] Give practice for disarming traps. --- map.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/map.cpp b/map.cpp index 781f820b5b..71700edcf3 100644 --- a/map.cpp +++ b/map.cpp @@ -1054,13 +1054,22 @@ void map::disarm_trap(game *g, int x, int y) add_item(x, y, g->itypes[comp[i]], 0); } tr_at(x, y) = tr_null; - } else if (roll >= diff * .8) + if(diff > 1.25*g->u.sklevel[sk_traps]) // failure might have set off trap + g->u.practice(sk_traps, 1.5*(diff - g->u.sklevel[sk_traps])); + } else if (roll >= diff * .8) { g->add_msg("You fail to disarm the trap."); + if(diff > 1.25*g->u.sklevel[sk_traps]) + g->u.practice(sk_traps, 1.5*(diff - g->u.sklevel[sk_traps])); + } else { g->add_msg("You fail to disarm the trap, and you set it off!"); trap* tr = g->traps[tr_at(x, y)]; trapfunc f; (f.*(tr->act))(g, x, y); + if(diff - roll <= 6) + // Give xp for failing, but not if we failed terribly (in which + // case the trap may not be disarmable). + g->u.practice(sk_traps, 2*diff); } } From 228c02ebe8359b1f7ebb4e9f6f999e10d83b9d47 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Wed, 6 Jun 2012 09:43:04 -0400 Subject: [PATCH 26/51] Fix map generation bug --- mapgen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapgen.cpp b/mapgen.cpp index 6159114b4a..a1bb2ba2c8 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -1085,7 +1085,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, house_room(this, room_bathroom, lw, cw, lw + 3, bw); house_room(this, room_bedroom, lw + 3, cw, rw, bw); if (one_in(4)) - ter(rng(lw + 1, lw + 2), bw - 2) = t_door_c; + ter(rng(lw + 1, lw + 2), cw) = t_door_c; else ter(lw + 3, rng(cw + 2, bw - 2)) = t_door_c; rn = rng(lw + 4, rw - 2); From 7df171d306c3c7d1b3fdc17579f9a0deb3ef6686 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Wed, 6 Jun 2012 09:53:33 -0400 Subject: [PATCH 27/51] Undo vehicle changes (these should have only been in mergey) --- game.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/game.cpp b/game.cpp index 6e0c238caf..49c3b465dc 100644 --- a/game.cpp +++ b/game.cpp @@ -813,10 +813,6 @@ void game::cancel_activity_query(const char* message, ...) if (query_yn("%s Stop construction?", s.c_str())) u.activity.type = ACT_NULL; break; - case ACT_VEHICLE: - if (query_yn("%s Stop construction?", s.c_str())) - u.activity.type = ACT_NULL; - break; default: u.activity.type = ACT_NULL; } From dcb65cc2f9af56409d2b0aea35ae04e510fc8406 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 9 Jun 2012 11:26:19 -0400 Subject: [PATCH 28/51] Trying out horde messages as less intrusive again. --- west.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/west.cpp b/west.cpp index c65dd6be87..0d0e5c06ec 100644 --- a/west.cpp +++ b/west.cpp @@ -195,16 +195,16 @@ void west_game::per_turn(game *g) g->u.rem_disease(DI_LYING_DOWN); } else if (dh < 30) { - if(g->u.has_disease(DI_SLEEP) || g->u.has_disease(DI_LYING_DOWN)) + // if(g->u.has_disease(DI_SLEEP) || g->u.has_disease(DI_LYING_DOWN)) g->add_msg("The distant horde approaches."); - else - popup("The distant horde approaches."); + // else + // popup("The distant horde approaches."); } else { - if(g->u.has_disease(DI_SLEEP) || g->u.has_disease(DI_LYING_DOWN)) + // if(g->u.has_disease(DI_SLEEP) || g->u.has_disease(DI_LYING_DOWN)) g->add_msg("In the far distance, the horde moves."); - else - popup("In the far distance, the horde moves ever forward."); + // else + // popup("In the far distance, the horde moves ever forward."); } } } From dfa873372f5275c22adba641ea800766986ceaea Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sat, 9 Jun 2012 11:27:16 -0400 Subject: [PATCH 29/51] code cleanup --- west.cpp | 1281 ------------------------------------------------------ 1 file changed, 1281 deletions(-) diff --git a/west.cpp b/west.cpp index 0d0e5c06ec..1efab94d4b 100644 --- a/west.cpp +++ b/west.cpp @@ -8,48 +8,6 @@ #include #include -// #define SPECIAL_WAVE_CHANCE 5 // One in X chance of single-flavor wave -// #define SPECIAL_WAVE_MIN 5 // Don't use a special wave with < X monsters - -// #define SELCOL(n) (selection == (n) ? c_yellow : c_blue) -// #define TOGCOL(n, b) (selection == (n) ? (b ? c_ltgreen : c_yellow) :\ -// (b ? c_green : c_dkgray)) -// #define NUMALIGN(n) ((n) >= 10000 ? 20 : ((n) >= 1000 ? 21 :\ -// ((n) >= 100 ? 22 : ((n) >= 10 ? 23 : 24)))) - -// std::string caravan_category_name(caravan_category cat); -// std::vector caravan_items(caravan_category cat); - -// int caravan_price(player &u, int price); - -// void draw_caravan_borders(WINDOW *w, int current_window); -// void draw_caravan_categories(WINDOW *w, int category_selected, int total_price, -// int cash); -// void draw_caravan_items(WINDOW *w, game *g, std::vector *items, -// std::vector *counts, int offset, -// int item_selected); - -// std::string defense_style_name(defense_style style); -// std::string defense_style_description(defense_style style); -// std::string defense_location_name(defense_location location); -// std::string defense_location_description(defense_location location); - -// defense_game::defense_game() -// { -// current_wave = 0; -// hunger = false; -// thirst = false; -// sleep = false; -// zombies = false; -// specials = false; -// spiders = false; -// triffids = false; -// robots = false; -// subspace = false; -// mercenaries = false; -// init_to_style(DEFENSE_EASY); -// } - bool west_game::init (game *g) { if(!g->u.create(g, PLTYPE_CUSTOM)) @@ -119,40 +77,6 @@ int west_game::distance_to_horde(game *g) { return g->global_location().x - horde_location; } -// bool defense_game::init(game *g) -// { -// g->turn = HOURS(12); // Start at noon -// g->temperature = 65; -// if (!g->u.create(g, PLTYPE_CUSTOM)) -// return false; -// g->u.str_cur = g->u.str_max; -// g->u.per_cur = g->u.per_max; -// g->u.int_cur = g->u.int_max; -// g->u.dex_cur = g->u.dex_max; -// init_itypes(g); -// init_mtypes(g); -// init_constructions(g); -// init_recipes(g); -// current_wave = 0; -// hunger = false; -// thirst = false; -// sleep = false; -// zombies = false; -// specials = false; -// spiders = false; -// triffids = false; -// robots = false; -// subspace = false; -// mercenaries = false; -// init_to_style(DEFENSE_EASY); -// setup(); -// g->u.cash = initial_cash; -// popup_nowait("Please wait as the map generates [ 0%]"); -// init_map(g); -// caravan(g); -// return true; -// } - void west_game::per_turn(game *g) { auto spam_zombies = [&] (int n = 200, bool fast_only = false) { @@ -215,1208 +139,3 @@ void west_game::pre_action(game *g, action_id &act) g->u.cash = horde_location; } } - -// if (act == ACTION_SLEEP && !sleep) { -// g->add_msg("You don't need to sleep!"); -// act = ACTION_NULL; -// } - -// // Big ugly block for movement -// if ((act == ACTION_MOVE_N && g->u.posy == SEEX * int(MAPSIZE / 2) && -// g->levy <= 93) || -// (act == ACTION_MOVE_NE && ((g->u.posy == SEEY * int(MAPSIZE / 2) && -// g->levy <= 93) || -// (g->u.posx == SEEX * (1 + int(MAPSIZE / 2))-1 && -// g->levx >= 98))) || -// (act == ACTION_MOVE_E && g->u.posx == SEEX * (1 + int(MAPSIZE / 2)) - 1 && -// g->levx >= 98) || -// (act == ACTION_MOVE_SE && ((g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && -// g->levy >= 98) || -// (g->u.posx == SEEX * (1 + int(MAPSIZE / 2))-1 && -// g->levx >= 98))) || -// (act == ACTION_MOVE_S && g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && -// g->levy >= 98) || -// (act == ACTION_MOVE_SW && ((g->u.posy == SEEY * (1 + int(MAPSIZE / 2))-1 && -// g->levy >= 98) || -// (g->u.posx == SEEX * int(MAPSIZE / 2) && -// g->levx <= 93))) || -// (act == ACTION_MOVE_W && g->u.posx == SEEX * int(MAPSIZE / 2) && -// g->levx <= 93) || -// (act == ACTION_MOVE_NW && ((g->u.posy == SEEY * int(MAPSIZE / 2) && -// g->levy <= 93) || -// (g->u.posx == SEEX * int(MAPSIZE / 2) && -// g->levx <= 93)))) { -// g->add_msg("You cannot leave the %s behind!", -// defense_location_name(location).c_str()); -// act = ACTION_NULL; -// } -// } - -// void defense_game::post_action(game *g, action_id act) -// { -// } - -// void defense_game::game_over(game *g) -// { -// popup("You managed to survive through wave %d!", current_wave); -// } - -// void defense_game::init_itypes(game *g) -// { -// g->itypes[itm_2x4]->volume = 0; -// g->itypes[itm_2x4]->weight = 0; -// g->itypes[itm_landmine]->price = 300; -// g->itypes[itm_bot_turret]->price = 6000; -// } - -// void defense_game::init_mtypes(game *g) -// { -// for (int i = 0; i < num_monsters; i++) { -// g->mtypes[i]->difficulty *= 1.5; -// g->mtypes[i]->difficulty += int(g->mtypes[i]->difficulty / 5); -// g->mtypes[i]->flags.push_back(MF_BASHES); -// g->mtypes[i]->flags.push_back(MF_SMELLS); -// g->mtypes[i]->flags.push_back(MF_HEARS); -// g->mtypes[i]->flags.push_back(MF_SEES); -// } -// } - -// void defense_game::init_constructions(game *g) -// { -// for (int i = 0; i < g->constructions.size(); i++) { -// for (int j = 0; j < g->constructions[i]->stages.size(); j++) { -// g->constructions[i]->stages[j].time = 1; // Everything takes 1 minute -// } -// } -// } - -// void defense_game::init_recipes(game *g) -// { -// for (int i = 0; i < g->recipes.size(); i++) { -// g->recipes[i]->time /= 10; // Things take turns, not minutes -// } -// } - -// void defense_game::init_map(game *g) -// { -// for (int x = 0; x < OMAPX; x++) { -// for (int y = 0; y < OMAPY; y++) { -// g->cur_om.ter(x, y) = ot_field; -// g->cur_om.seen(x, y) = true; -// } -// } - -// g->cur_om.save(g->u.name, 0, 0, DEFENSE_Z); -// g->levx = 100; -// g->levy = 100; -// g->levz = 0; -// g->u.posx = SEEX; -// g->u.posy = SEEY; - -// switch (location) { - -// case DEFLOC_HOSPITAL: -// for (int x = 49; x <= 51; x++) { -// for (int y = 49; y <= 51; y++) -// g->cur_om.ter(x, y) = ot_hospital; -// } -// g->cur_om.ter(50, 49) = ot_hospital_entrance; -// break; - -// case DEFLOC_MALL: -// for (int x = 49; x <= 51; x++) { -// for (int y = 49; y <= 51; y++) -// g->cur_om.ter(x, y) = ot_megastore; -// } -// g->cur_om.ter(50, 49) = ot_megastore_entrance; -// break; - -// case DEFLOC_BAR: -// g->cur_om.ter(50, 50) = ot_bar_north; -// break; - -// case DEFLOC_MANSION: -// for (int x = 49; x <= 51; x++) { -// for (int y = 49; y <= 51; y++) -// g->cur_om.ter(x, y) = ot_mansion; -// } -// g->cur_om.ter(50, 49) = ot_mansion_entrance; -// break; -// } -// // Init the map -// int old_percent = 0; -// for (int i = 0; i <= MAPSIZE * 2; i += 2) { -// for (int j = 0; j <= MAPSIZE * 2; j += 2) { -// int mx = g->levx - MAPSIZE + i, my = g->levy - MAPSIZE + j; -// int percent = 100 * ((j / 2 + MAPSIZE * (i / 2))) / -// ((MAPSIZE) * (MAPSIZE + 1)); -// if (percent >= old_percent + 1) { -// popup_nowait("Please wait as the map generates [%s%d%]", -// (percent < 10 ? " " : ""), percent); -// old_percent = percent; -// } -// // Round down to the nearest even number -// mx -= mx % 2; -// my -= my % 2; -// tinymap tm(&g->itypes, &g->mapitems, &g->traps); -// tm.generate(g, &(g->cur_om), mx, my, int(g->turn)); -// tm.clear_spawns(); -// tm.clear_traps(); -// tm.save(&g->cur_om, int(g->turn), mx, my); -// } -// } - -// g->m.load(g, g->levx, g->levy); - -// g->update_map(g->u.posx, g->u.posy); -// monster generator(g->mtypes[mon_generator], g->u.posx + 1, g->u.posy + 1); -// // Find a valid spot to spawn the generator -// std::vector valid; -// for (int x = g->u.posx - 1; x <= g->u.posx + 1; x++) { -// for (int y = g->u.posy - 1; y <= g->u.posy + 1; y++) { -// if (generator.can_move_to(g->m, x, y) && g->is_empty(x, y)) -// valid.push_back( point(x, y) ); -// } -// } -// if (!valid.empty()) { -// point p = valid[rng(0, valid.size() - 1)]; -// generator.spawn(p.x, p.y); -// } -// generator.friendly = -1; -// g->z.push_back(generator); -// } - -// void defense_game::init_to_style(defense_style new_style) -// { -// style = new_style; -// hunger = false; -// thirst = false; -// sleep = false; -// zombies = false; -// specials = false; -// spiders = false; -// triffids = false; -// robots = false; -// subspace = false; -// mercenaries = false; - -// switch (new_style) { -// case DEFENSE_EASY: -// location = DEFLOC_HOSPITAL; -// initial_difficulty = 15; -// wave_difficulty = 10; -// time_between_waves = 30; -// waves_between_caravans = 3; -// initial_cash = 10000; -// cash_per_wave = 1000; -// cash_increase = 300; -// specials = true; -// spiders = true; -// triffids = true; -// mercenaries = true; -// break; - -// case DEFENSE_MEDIUM: -// location = DEFLOC_MALL; -// initial_difficulty = 30; -// wave_difficulty = 15; -// time_between_waves = 20; -// waves_between_caravans = 4; -// initial_cash = 6000; -// cash_per_wave = 800; -// cash_increase = 200; -// specials = true; -// spiders = true; -// triffids = true; -// robots = true; -// hunger = true; -// mercenaries = true; -// break; - -// case DEFENSE_HARD: -// location = DEFLOC_BAR; -// initial_difficulty = 50; -// wave_difficulty = 20; -// time_between_waves = 10; -// waves_between_caravans = 5; -// initial_cash = 2000; -// cash_per_wave = 600; -// cash_increase = 100; -// specials = true; -// spiders = true; -// triffids = true; -// robots = true; -// subspace = true; -// hunger = true; -// thirst = true; -// break; - -// break; - -// case DEFENSE_SHAUN: -// location = DEFLOC_BAR; -// initial_difficulty = 30; -// wave_difficulty = 15; -// time_between_waves = 5; -// waves_between_caravans = 6; -// initial_cash = 5000; -// cash_per_wave = 500; -// cash_increase = 100; -// zombies = true; -// break; - -// case DEFENSE_DAWN: -// location = DEFLOC_MALL; -// initial_difficulty = 60; -// wave_difficulty = 20; -// time_between_waves = 30; -// waves_between_caravans = 4; -// initial_cash = 8000; -// cash_per_wave = 500; -// cash_increase = 0; -// zombies = true; -// hunger = true; -// thirst = true; -// mercenaries = true; -// break; - -// case DEFENSE_SPIDERS: -// location = DEFLOC_MALL; -// initial_difficulty = 60; -// wave_difficulty = 10; -// time_between_waves = 10; -// waves_between_caravans = 4; -// initial_cash = 6000; -// cash_per_wave = 500; -// cash_increase = 100; -// spiders = true; -// break; - -// case DEFENSE_TRIFFIDS: -// location = DEFLOC_MANSION; -// initial_difficulty = 60; -// wave_difficulty = 20; -// time_between_waves = 30; -// waves_between_caravans = 2; -// initial_cash = 10000; -// cash_per_wave = 600; -// cash_increase = 100; -// triffids = true; -// hunger = true; -// thirst = true; -// sleep = true; -// mercenaries = true; -// break; - -// case DEFENSE_SKYNET: -// location = DEFLOC_HOSPITAL; -// initial_difficulty = 20; -// wave_difficulty = 20; -// time_between_waves = 20; -// waves_between_caravans = 6; -// initial_cash = 12000; -// cash_per_wave = 1000; -// cash_increase = 200; -// robots = true; -// hunger = true; -// thirst = true; -// mercenaries = true; -// break; - -// case DEFENSE_LOVECRAFT: -// location = DEFLOC_MANSION; -// initial_difficulty = 20; -// wave_difficulty = 20; -// time_between_waves = 120; -// waves_between_caravans = 8; -// initial_cash = 4000; -// cash_per_wave = 1000; -// cash_increase = 100; -// subspace = true; -// hunger = true; -// thirst = true; -// sleep = true; -// } -// } - -// void defense_game::setup() -// { -// WINDOW* w = newwin(25, 80, 0, 0); -// int selection = 1; -// refresh_setup(w, selection); - -// while (true) { -// char ch = input(); - -// if (ch == 'S') { -// if (!zombies && !specials && !spiders && !triffids && !robots && !subspace) { -// popup("You must choose at least one monster group!"); -// refresh_setup(w, selection); -// } else -// return; -// } else if (ch == '+' || ch == '>' || ch == 'j') { -// if (selection == 19) -// selection = 1; -// else -// selection++; -// refresh_setup(w, selection); -// } else if (ch == '-' || ch == '<' || ch == 'k') { -// if (selection == 1) -// selection = 19; -// else -// selection--; -// refresh_setup(w, selection); -// } else if (ch == '!') { -// std::string name = string_input_popup(20, "Template Name:"); -// refresh_setup(w, selection); -// } else if (ch == 'S') -// return; - -// else { -// switch (selection) { -// case 1: // Scenario selection -// if (ch == 'l') { -// if (style == defense_style(NUM_DEFENSE_STYLES - 1)) -// style = defense_style(1); -// else -// style = defense_style(style + 1); -// } -// if (ch == 'h') { -// if (style == defense_style(1)) -// style = defense_style(NUM_DEFENSE_STYLES - 1); -// else -// style = defense_style(style - 1); -// } -// init_to_style(style); -// break; - -// case 2: // Location selection -// if (ch == 'l') { -// if (location == defense_location(NUM_DEFENSE_LOCATIONS - 1)) -// location = defense_location(1); -// else -// location = defense_location(location + 1); -// } -// if (ch == 'h') { -// if (location == defense_location(1)) -// location = defense_location(NUM_DEFENSE_LOCATIONS - 1); -// else -// location = defense_location(location - 1); -// } -// mvwprintz(w, 5, 2, c_black, "\ -// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); -// mvwprintz(w, 5, 2, c_yellow, defense_location_name(location).c_str()); -// mvwprintz(w, 5, 28, c_ltgray, -// defense_location_description(location).c_str()); -// break; - -// case 3: // Difficulty of the first wave -// if (ch == 'h' && initial_difficulty > 10) -// initial_difficulty -= 5; -// if (ch == 'l' && initial_difficulty < 995) -// initial_difficulty += 5; -// mvwprintz(w, 7, 22, c_black, "xxx"); -// mvwprintz(w, 7, NUMALIGN(initial_difficulty), c_yellow, "%d", -// initial_difficulty); -// break; - -// case 4: // Wave Difficulty -// if (ch == 'h' && wave_difficulty > 10) -// wave_difficulty -= 5; -// if (ch == 'l' && wave_difficulty < 995) -// wave_difficulty += 5; -// mvwprintz(w, 8, 22, c_black, "xxx"); -// mvwprintz(w, 8, NUMALIGN(wave_difficulty), c_yellow, "%d", -// wave_difficulty); -// break; - -// case 5: -// if (ch == 'h' && time_between_waves > 5) -// time_between_waves -= 5; -// if (ch == 'l' && time_between_waves < 995) -// time_between_waves += 5; -// mvwprintz(w, 10, 22, c_black, "xxx"); -// mvwprintz(w, 10, NUMALIGN(time_between_waves), c_yellow, "%d", -// time_between_waves); -// break; - -// case 6: -// if (ch == 'h' && waves_between_caravans > 1) -// waves_between_caravans -= 1; -// if (ch == 'l' && waves_between_caravans < 50) -// waves_between_caravans += 1; -// mvwprintz(w, 11, 22, c_black, "xxx"); -// mvwprintz(w, 11, NUMALIGN(waves_between_caravans), c_yellow, "%d", -// waves_between_caravans); -// break; - -// case 7: -// if (ch == 'h' && initial_cash > 0) -// initial_cash -= 100; -// if (ch == 'l' && initial_cash < 99900) -// initial_cash += 100; -// mvwprintz(w, 13, 20, c_black, "xxxxx"); -// mvwprintz(w, 13, NUMALIGN(initial_cash), c_yellow, "%d", initial_cash); -// break; - -// case 8: -// if (ch == 'h' && cash_per_wave > 0) -// cash_per_wave -= 100; -// if (ch == 'l' && cash_per_wave < 9900) -// cash_per_wave += 100; -// mvwprintz(w, 14, 21, c_black, "xxxx"); -// mvwprintz(w, 14, NUMALIGN(cash_per_wave), c_yellow, "%d", cash_per_wave); -// break; - -// case 9: -// if (ch == 'h' && cash_increase > 0) -// cash_increase -= 50; -// if (ch == 'l' && cash_increase < 9950) -// cash_increase += 50; -// mvwprintz(w, 15, 21, c_black, "xxxx"); -// mvwprintz(w, 15, NUMALIGN(cash_increase), c_yellow, "%d", cash_increase); -// break; - -// case 10: -// if (ch == ' ' || ch == '\n') { -// zombies = !zombies; -// specials = false; -// } -// mvwprintz(w, 18, 2, (zombies ? c_ltgreen : c_yellow), "Zombies"); -// mvwprintz(w, 18, 14, c_yellow, "Special Zombies"); -// break; - -// case 11: -// if (ch == ' ' || ch == '\n') { -// specials = !specials; -// zombies = false; -// } -// mvwprintz(w, 18, 2, c_yellow, "Zombies"); -// mvwprintz(w, 18, 14, (specials ? c_ltgreen : c_yellow), "Special Zombies"); -// break; - -// case 12: -// if (ch == ' ' || ch == '\n') -// spiders = !spiders; -// mvwprintz(w, 18, 34, (spiders ? c_ltgreen : c_yellow), "Spiders"); -// break; - -// case 13: -// if (ch == ' ' || ch == '\n') -// triffids = !triffids; -// mvwprintz(w, 18, 46, (triffids ? c_ltgreen : c_yellow), "Triffids"); -// break; - -// case 14: -// if (ch == ' ' || ch == '\n') -// robots = !robots; -// mvwprintz(w, 18, 59, (robots ? c_ltgreen : c_yellow), "Robots"); -// break; - -// case 15: -// if (ch == ' ' || ch == '\n') -// subspace = !subspace; -// mvwprintz(w, 18, 70, (subspace ? c_ltgreen : c_yellow), "Subspace"); -// break; - -// case 16: -// if (ch == ' ' || ch == '\n') -// hunger = !hunger; -// mvwprintz(w, 21, 2, (hunger ? c_ltgreen : c_yellow), "Food"); -// break; - -// case 17: -// if (ch == ' ' || ch == '\n') -// thirst = !thirst; -// mvwprintz(w, 21, 16, (thirst ? c_ltgreen : c_yellow), "Water"); -// break; - -// case 18: -// if (ch == ' ' || ch == '\n') -// sleep = !sleep; -// mvwprintz(w, 21, 31, (sleep ? c_ltgreen : c_yellow), "Sleep"); -// break; - -// case 19: -// if (ch == ' ' || ch == '\n') -// mercenaries = !mercenaries; -// mvwprintz(w, 21, 46, (mercenaries ? c_ltgreen : c_yellow), "Mercenaries"); -// break; -// } -// } -// if (ch == 'h' || ch == 'l' || ch == ' ' || ch == '\n') -// refresh_setup(w, selection); -// } -// } - -// void defense_game::refresh_setup(WINDOW* w, int selection) -// { -// werase(w); -// mvwprintz(w, 0, 1, c_ltred, "DEFENSE MODE"); -// mvwprintz(w, 0, 28, c_ltred, "Press +/- or >/< to cycle, spacebar to toggle"); -// mvwprintz(w, 1, 28, c_ltred, "Press S to start, ! to save as a template"); -// mvwprintz(w, 2, 2, c_ltgray, "Scenario:"); -// mvwprintz(w, 3, 2, SELCOL(1), defense_style_name(style).c_str()); -// mvwprintz(w, 3, 28, c_ltgray, defense_style_description(style).c_str()); -// mvwprintz(w, 4, 2, c_ltgray, "Location:"); -// mvwprintz(w, 5, 2, SELCOL(2), defense_location_name(location).c_str()); -// mvwprintz(w, 5, 28, c_ltgray, defense_location_description(location).c_str()); - -// mvwprintz(w, 7, 2, c_ltgray, "Initial Difficulty:"); -// mvwprintz(w, 7, NUMALIGN(initial_difficulty), SELCOL(3), "%d", -// initial_difficulty); -// mvwprintz(w, 7, 28, c_ltgray, "The difficulty of the first wave."); -// mvwprintz(w, 8, 2, c_ltgray, "Wave Difficulty:"); -// mvwprintz(w, 8, NUMALIGN(wave_difficulty), SELCOL(4), "%d", wave_difficulty); -// mvwprintz(w, 8, 28, c_ltgray, "The increase of difficulty with each wave."); - -// mvwprintz(w, 10, 2, c_ltgray, "Time b/w Waves:"); -// mvwprintz(w, 10, NUMALIGN(time_between_waves), SELCOL(5), "%d", -// time_between_waves); -// mvwprintz(w, 10, 28, c_ltgray, "The time, in minutes, between waves."); -// mvwprintz(w, 11, 2, c_ltgray, "Waves b/w Caravans:"); -// mvwprintz(w, 11, NUMALIGN(waves_between_caravans), SELCOL(6), "%d", -// waves_between_caravans); -// mvwprintz(w, 11, 28, c_ltgray, "The number of waves in between caravans."); - -// mvwprintz(w, 13, 2, c_ltgray, "Initial Cash:"); -// mvwprintz(w, 13, NUMALIGN(initial_cash), SELCOL(7), "%d", initial_cash); -// mvwprintz(w, 13, 28, c_ltgray, "The amount of money the player starts with."); -// mvwprintz(w, 14, 2, c_ltgray, "Cash for 1st Wave:"); -// mvwprintz(w, 14, NUMALIGN(cash_per_wave), SELCOL(8), "%d", cash_per_wave); -// mvwprintz(w, 14, 28, c_ltgray, "The cash awarded for the first wave."); -// mvwprintz(w, 15, 2, c_ltgray, "Cash Increase:"); -// mvwprintz(w, 15, NUMALIGN(cash_increase), SELCOL(9), "%d", cash_increase); -// mvwprintz(w, 15, 28, c_ltgray, "The increase in the award each wave."); - -// mvwprintz(w, 17, 2, c_ltgray, "Enemy Selection:"); -// mvwprintz(w, 18, 2, TOGCOL(10, zombies), "Zombies"); -// mvwprintz(w, 18, 14, TOGCOL(11, specials), "Special Zombies"); -// mvwprintz(w, 18, 34, TOGCOL(12, spiders), "Spiders"); -// mvwprintz(w, 18, 46, TOGCOL(13, triffids), "Triffids"); -// mvwprintz(w, 18, 59, TOGCOL(14, robots), "Robots"); -// mvwprintz(w, 18, 70, TOGCOL(15, subspace), "Subspace"); - -// mvwprintz(w, 20, 2, c_ltgray, "Needs:"); -// mvwprintz(w, 21, 2, TOGCOL(16, hunger), "Food"); -// mvwprintz(w, 21, 16, TOGCOL(17, thirst), "Water"); -// mvwprintz(w, 21, 31, TOGCOL(18, sleep), "Sleep"); -// mvwprintz(w, 21, 46, TOGCOL(19, mercenaries), "Mercenaries"); -// wrefresh(w); -// } - -// std::string defense_style_name(defense_style style) -// { -// // 24 Characters Max! -// switch (style) { -// case DEFENSE_CUSTOM: return "Custom"; -// case DEFENSE_EASY: return "Easy"; -// case DEFENSE_MEDIUM: return "Medium"; -// case DEFENSE_HARD: return "Hard"; -// case DEFENSE_SHAUN: return "Shaun of the Dead"; -// case DEFENSE_DAWN: return "Dawn of the Dead"; -// case DEFENSE_SPIDERS: return "Eight-Legged Freaks"; -// case DEFENSE_TRIFFIDS: return "Day of the Triffids"; -// case DEFENSE_SKYNET: return "Skynet"; -// case DEFENSE_LOVECRAFT: return "The Call of Cthulhu"; -// default: return "Bug! Report this!"; -// } -// } - -// std::string defense_style_description(defense_style style) -// { -// // 51 Characters Max! -// switch (style) { -// case DEFENSE_CUSTOM: -// return "A custom game."; -// case DEFENSE_EASY: -// return "Easy monsters and lots of money."; -// case DEFENSE_MEDIUM: -// return "Harder monsters. You have to eat."; -// case DEFENSE_HARD: -// return "All monsters. You have to eat and drink."; -// case DEFENSE_SHAUN: -// return "Defend a bar against classic zombies. Easy and fun."; -// case DEFENSE_DAWN: -// return "Classic zombies. Slower and more realistic."; -// case DEFENSE_SPIDERS: -// return "Fast-paced spider-fighting fun!"; -// case DEFENSE_TRIFFIDS: -// return "Defend your mansion against the triffids."; -// case DEFENSE_SKYNET: -// return "The robots have decided that humans are the enemy!"; -// case DEFENSE_LOVECRAFT: -// return "Ward off legions of eldritch horrors."; -// default: -// return "What the heck is this I don't even know. A bug!"; -// } -// } - -// std::string defense_location_name(defense_location location) -// { -// switch (location) { -// case DEFLOC_NULL: return "Nowhere?! A bug!"; -// case DEFLOC_HOSPITAL: return "Hospital"; -// case DEFLOC_MALL: return "Megastore"; -// case DEFLOC_BAR: return "Bar"; -// case DEFLOC_MANSION: return "Mansion"; -// default: return "a ghost's house (bug)"; -// } -// } - -// std::string defense_location_description(defense_location location) -// { -// switch (location) { -// case DEFLOC_NULL: -// return "NULL Bug."; -// case DEFLOC_HOSPITAL: -// return "One entrance and many rooms. Some medical supplies."; -// case DEFLOC_MALL: -// return "A large building with various supplies."; -// case DEFLOC_BAR: -// return "A small building with plenty of alcohol."; -// case DEFLOC_MANSION: -// return "A large house with many rooms and."; -// default: -// return "Unknown data bug."; -// } -// } - -// void defense_game::caravan(game *g) -// { -// caravan_category tab = CARAVAN_MELEE; -// std::vector items[NUM_CARAVAN_CATEGORIES]; -// std::vector item_count[NUM_CARAVAN_CATEGORIES]; - -// // Init the items for each category -// for (int i = 0; i < NUM_CARAVAN_CATEGORIES; i++) { -// items[i] = caravan_items( caravan_category(i) ); -// for (int j = 0; j < items[i].size(); j++) { -// if (current_wave == 0 || !one_in(4)) -// item_count[i].push_back(0); // Init counts to 0 for each item -// else { // Remove the item -// items[i].erase( items[i].begin() + j); -// j--; -// } -// } -// } - -// int total_price = 0; - -// WINDOW *w = newwin(25, 80, 0, 0); - -// int offset = 0, item_selected = 0, category_selected = 0; - -// int current_window = 0; - -// draw_caravan_borders(w, current_window); -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); - -// bool done = false; -// bool cancel = false; -// while (!done) { - -// char ch = input(); -// switch (ch) { -// case '?': -// popup_top("\ -// CARAVAN:\n\ -// Start by selecting a category using your favorite up/down keys.\n\ -// Switch between category selection and item selecting by pressing Tab.\n\ -// Pick an item with the up/down keys, press + to buy 1 more, - to buy 1 less.\n\ -// Press Enter to buy everything in your cart, Esc to buy nothing."); -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, item_selected); -// draw_caravan_borders(w, current_window); -// break; - -// case 'j': -// if (current_window == 0) { // Categories -// category_selected++; -// if (category_selected == NUM_CARAVAN_CATEGORIES) -// category_selected = CARAVAN_CART; -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// offset = 0; -// item_selected = 0; -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, -// item_selected); -// draw_caravan_borders(w, current_window); -// } else if (items[category_selected].size() > 0) { // Items -// if (item_selected < items[category_selected].size() - 1) -// item_selected++; -// else { -// item_selected = 0; -// offset = 0; -// } -// if (item_selected > offset + 22) -// offset++; -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, -// item_selected); -// draw_caravan_borders(w, current_window); -// } -// break; - -// case 'k': -// if (current_window == 0) { // Categories -// if (category_selected == 0) -// category_selected = NUM_CARAVAN_CATEGORIES - 1; -// else -// category_selected--; -// if (category_selected == NUM_CARAVAN_CATEGORIES) -// category_selected = CARAVAN_CART; -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// offset = 0; -// item_selected = 0; -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, -// item_selected); -// draw_caravan_borders(w, current_window); -// } else if (items[category_selected].size() > 0) { // Items -// if (item_selected > 0) -// item_selected--; -// else { -// item_selected = items[category_selected].size() - 1; -// offset = item_selected - 22; -// if (offset < 0) -// offset = 0; -// } -// if (item_selected < offset) -// offset--; -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, -// item_selected); -// draw_caravan_borders(w, current_window); -// } -// break; - -// case '+': -// case 'l': -// if (current_window == 1 && items[category_selected].size() > 0) { -// item_count[category_selected][item_selected]++; -// itype_id tmp_itm = items[category_selected][item_selected]; -// total_price += caravan_price(g->u, g->itypes[tmp_itm]->price); -// if (category_selected == CARAVAN_CART) { // Find the item in its category -// for (int i = 1; i < NUM_CARAVAN_CATEGORIES; i++) { -// for (int j = 0; j < items[i].size(); j++) { -// if (items[i][j] == tmp_itm) -// item_count[i][j]++; -// } -// } -// } else { // Add / increase the item in the shopping cart -// bool found_item = false; -// for (int i = 0; i < items[0].size() && !found_item; i++) { -// if (items[0][i] == tmp_itm) { -// found_item = true; -// item_count[0][i]++; -// } -// } -// if (!found_item) { -// items[0].push_back(items[category_selected][item_selected]); -// item_count[0].push_back(1); -// } -// } -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, item_selected); -// draw_caravan_borders(w, current_window); -// } -// break; - -// case '-': -// case 'h': -// if (current_window == 1 && items[category_selected].size() > 0 && -// item_count[category_selected][item_selected] > 0) { -// item_count[category_selected][item_selected]--; -// itype_id tmp_itm = items[category_selected][item_selected]; -// total_price -= caravan_price(g->u, g->itypes[tmp_itm]->price); -// if (category_selected == CARAVAN_CART) { // Find the item in its category -// for (int i = 1; i < NUM_CARAVAN_CATEGORIES; i++) { -// for (int j = 0; j < items[i].size(); j++) { -// if (items[i][j] == tmp_itm) -// item_count[i][j]--; -// } -// } -// } else { // Decrease / remove the item in the shopping cart -// bool found_item = false; -// for (int i = 0; i < items[0].size() && !found_item; i++) { -// if (items[0][i] == tmp_itm) { -// found_item = true; -// item_count[0][i]--; -// if (item_count[0][i] == 0) { -// item_count[0].erase(item_count[0].begin() + i); -// items[0].erase(items[0].begin() + i); -// } -// } -// } -// } -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, item_selected); -// draw_caravan_borders(w, current_window); -// } -// break; - -// case '\t': -// current_window = (current_window + 1) % 2; -// draw_caravan_borders(w, current_window); -// break; - -// case KEY_ESCAPE: -// if (query_yn("Really buy nothing?")) { -// cancel = true; -// done = true; -// } else { -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, item_selected); -// draw_caravan_borders(w, current_window); -// } -// break; - -// case '\n': -// if (total_price > g->u.cash) -// popup("You can't afford those items!"); -// else if ((items[0].empty() && query_yn("Really buy nothing?")) || -// (!items[0].empty() && -// query_yn("Buy %d items, leaving you with $%d?", items[0].size(), -// g->u.cash - total_price))) -// done = true; -// if (!done) { // We canceled, so redraw everything -// draw_caravan_categories(w, category_selected, total_price, g->u.cash); -// draw_caravan_items(w, g, &(items[category_selected]), -// &(item_count[category_selected]), offset, item_selected); -// draw_caravan_borders(w, current_window); -// } -// break; -// } // switch (ch) - -// } // while (!done) - -// if (!cancel) { -// g->u.cash -= total_price; -// bool dropped_some = false; -// for (int i = 0; i < items[0].size(); i++) { -// item tmp(g->itypes[ items[0][i] ], g->turn); -// tmp = tmp.in_its_container(&(g->itypes)); -// for (int j = 0; j < item_count[0][i]; j++) { -// if (g->u.volume_carried() + tmp.volume() <= g->u.volume_capacity() && -// g->u.weight_carried() + tmp.weight() <= g->u.weight_capacity() && -// g->u.inv.size() < 52) -// g->u.i_add(tmp); -// else { // Could fit it in the inventory! -// dropped_some = true; -// g->m.add_item(g->u.posx, g->u.posy, tmp); -// } -// } -// } -// if (dropped_some) -// g->add_msg("You drop some items."); -// } -// } - -// std::string caravan_category_name(caravan_category cat) -// { -// switch (cat) { -// case CARAVAN_CART: return "Shopping Cart"; -// case CARAVAN_MELEE: return "Melee Weapons"; -// case CARAVAN_GUNS: return "Firearms & Ammo"; -// case CARAVAN_COMPONENTS: return "Crafting & Construction Components"; -// case CARAVAN_FOOD: return "Food & Drugs"; -// case CARAVAN_CLOTHES: return "Clothing & Armor"; -// case CARAVAN_TOOLS: return "Tools, Traps & Grenades"; -// } -// return "BUG"; -// } - -// std::vector caravan_items(caravan_category cat) -// { -// std::vector ret; -// switch (cat) { -// case CARAVAN_CART: -// return ret; - -// case CARAVAN_MELEE: -// setvector(ret, -// itm_hammer, itm_bat, itm_mace, itm_morningstar, itm_hammer_sledge, itm_hatchet, -// itm_knife_combat, itm_rapier, itm_machete, itm_katana, itm_spear_knife, -// itm_pike, itm_chainsaw_off, NULL); -// break; - -// case CARAVAN_GUNS: -// setvector(ret, -// itm_crossbow, itm_bolt_steel, itm_compbow, itm_arrow_cf, itm_marlin_9a, -// itm_22_lr, itm_hk_mp5, itm_9mm, itm_taurus_38, itm_38_special, itm_deagle_44, -// itm_44magnum, itm_m1911, itm_hk_ump45, itm_45_acp, itm_fn_p90, itm_57mm, -// itm_remington_870, itm_shot_00, itm_shot_slug, itm_browning_blr, itm_3006, -// itm_ak47, itm_762_m87, itm_m4a1, itm_556, itm_savage_111f, itm_hk_g3, -// itm_762_51, itm_hk_g80, itm_12mm, itm_plasma_rifle, itm_plasma, NULL); -// break; - -// case CARAVAN_COMPONENTS: -// setvector(ret, -// itm_rag, itm_fur, itm_leather, itm_superglue, itm_string_36, itm_chain, -// itm_processor, itm_RAM, itm_power_supply, itm_motor, itm_hose, itm_pot, -// itm_2x4, itm_battery, itm_nail, itm_gasoline, NULL); -// break; - -// case CARAVAN_FOOD: -// setvector(ret, -// itm_1st_aid, itm_water, itm_energy_drink, itm_whiskey, itm_can_beans, -// itm_mre_beef, itm_flour, itm_inhaler, itm_codeine, itm_oxycodone, itm_adderall, -// itm_cig, itm_meth, itm_royal_jelly, itm_mutagen, itm_purifier, NULL); -// break; - -// case CARAVAN_CLOTHES: -// setvector(ret, -// itm_backpack, itm_vest, itm_trenchcoat, itm_jacket_leather, itm_kevlar, -// itm_gloves_fingerless, itm_mask_filter, itm_mask_gas, itm_glasses_eye, -// itm_glasses_safety, itm_goggles_ski, itm_goggles_nv, itm_helmet_ball, -// itm_helmet_riot, NULL); -// break; - -// case CARAVAN_TOOLS: -// setvector(ret, -// itm_screwdriver, itm_wrench, itm_saw, itm_hacksaw, itm_lighter, itm_sewing_kit, -// itm_scissors, itm_extinguisher, itm_flashlight, itm_hotplate, -// itm_soldering_iron, itm_shovel, itm_jackhammer, itm_landmine, itm_teleporter, -// itm_grenade, itm_flashbang, itm_EMPbomb, itm_smokebomb, itm_bot_manhack, -// itm_bot_turret, itm_UPS_off, itm_mininuke, NULL); -// break; -// } - -// return ret; -// } - -// void draw_caravan_borders(WINDOW *w, int current_window) -// { -// // First, do the borders for the category window -// nc_color col = c_ltgray; -// if (current_window == 0) -// col = c_yellow; - -// mvwputch(w, 0, 0, col, LINE_OXXO); -// for (int i = 1; i <= 38; i++) { -// mvwputch(w, 0, i, col, LINE_OXOX); -// mvwputch(w, 11, i, col, LINE_OXOX); -// } -// for (int i = 1; i <= 10; i++) { -// mvwputch(w, i, 0, col, LINE_XOXO); -// mvwputch(w, i, 39, c_yellow, LINE_XOXO); // Shared border, always yellow -// } -// mvwputch(w, 11, 0, col, LINE_XXXO); - -// // These are shared with the items window, and so are always "on" -// mvwputch(w, 0, 39, c_yellow, LINE_OXXX); -// mvwputch(w, 11, 39, c_yellow, LINE_XOXX); - -// col = (current_window == 1 ? c_yellow : c_ltgray); -// // Next, draw the borders for the item description window--always "off" & gray -// for (int i = 12; i <= 23; i++) { -// mvwputch(w, i, 0, c_ltgray, LINE_XOXO); -// mvwputch(w, i, 39, col, LINE_XOXO); -// } -// for (int i = 1; i <= 38; i++) -// mvwputch(w, 24, i, c_ltgray, LINE_OXOX); - -// mvwputch(w, 24, 0, c_ltgray, LINE_XXOO); -// mvwputch(w, 24, 39, c_ltgray, LINE_XXOX); - -// // Finally, draw the item section borders -// for (int i = 40; i <= 78; i++) { -// mvwputch(w, 0, i, col, LINE_OXOX); -// mvwputch(w, 24, i, col, LINE_OXOX); -// } -// for (int i = 1; i <= 23; i++) -// mvwputch(w, i, 79, col, LINE_XOXO); - -// mvwputch(w, 24, 39, col, LINE_XXOX); -// mvwputch(w, 0, 79, col, LINE_OOXX); -// mvwputch(w, 24, 79, col, LINE_XOOX); - -// // Quick reminded about help. -// mvwprintz(w, 24, 2, c_red, "Press ? for help."); -// wrefresh(w); -// } - -// void draw_caravan_categories(WINDOW *w, int category_selected, int total_price, -// int cash) -// { -// // Clear the window -// for (int i = 1; i <= 10; i++) -// mvwprintz(w, i, 1, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); -// mvwprintz(w, 1, 1, c_white, "Your Cash:%s%d", -// (cash >= 10000 ? " " : (cash >= 1000 ? " " : (cash >= 100 ? " " : -// (cash >= 10 ? " " : " ")))), cash); -// wprintz(w, c_ltgray, " -> "); -// wprintz(w, (total_price > cash ? c_red : c_green), "%d", cash - total_price); - -// for (int i = 0; i < NUM_CARAVAN_CATEGORIES; i++) -// mvwprintz(w, i + 3, 1, (i == category_selected ? h_white : c_white), -// caravan_category_name( caravan_category(i) ).c_str()); -// wrefresh(w); -// } - -// void draw_caravan_items(WINDOW *w, game *g, std::vector *items, -// std::vector *counts, int offset, -// int item_selected) -// { -// // Print the item info first. This is important, because it contains \n which -// // will corrupt the item list. - -// // Actually, clear the item info first. -// for (int i = 12; i <= 23; i++) -// mvwprintz(w, i, 1, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); -// // THEN print it--if item_selected is valid -// if (item_selected < items->size()) { -// item tmp(g->itypes[ (*items)[item_selected] ], 0); // Dummy item to get info -// mvwprintz(w, 12, 0, c_white, tmp.info().c_str()); -// } -// // Next, clear the item list on the right -// for (int i = 1; i <= 23; i++) -// mvwprintz(w, i, 40, c_black, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); -// // Finally, print the item list on the right -// for (int i = offset; i <= offset + 23 && i < items->size(); i++) { -// mvwprintz(w, i - offset + 1, 40, (item_selected == i ? h_white : c_white), -// g->itypes[ (*items)[i] ]->name.c_str()); -// wprintz(w, c_white, " x %s%d", ((*counts)[i] >= 10 ? "" : " "), (*counts)[i]); -// if ((*counts)[i] > 0) { -// int price = caravan_price(g->u, g->itypes[(*items)[i]]->price *(*counts)[i]); -// wprintz(w, (price > g->u.cash ? c_red : c_green), -// "($%s%d)", (price >= 100000 ? "" : (price >= 10000 ? " " : -// (price >= 1000 ? " " : (price >= 100 ? " " : -// (price >= 10 ? " " : " "))))), price); -// } -// } -// wrefresh(w); -// } - -// int caravan_price(player &u, int price) -// { -// if (u.sklevel[sk_barter] > 10) -// return int( double(price) * .5); -// return int( double(price) * (1.0 - double(u.sklevel[sk_barter]) * .05)); -// } - -// void defense_game::spawn_wave(game *g) -// { -// g->add_msg("********"); -// int diff = initial_difficulty + current_wave * wave_difficulty; -// bool themed_wave = one_in(SPECIAL_WAVE_CHANCE); // All a single monster type -// g->u.cash += cash_per_wave + (current_wave - 1) * cash_increase; -// std::vector valid; -// valid = pick_monster_wave(g); -// while (diff > 0) { -// // Clear out any monsters that exceed our remaining difficulty -// for (int i = 0; i < valid.size(); i++) { -// if (g->mtypes[valid[i]]->difficulty > diff) { -// valid.erase(valid.begin() + i); -// i--; -// } -// } -// if (valid.size() == 0) { -// g->add_msg("Welcome to Wave %d!", current_wave); -// g->add_msg("********"); -// return; -// } -// int rn = rng(0, valid.size() - 1); -// mtype *type = g->mtypes[valid[rn]]; -// if (themed_wave) { -// int num = diff / type->difficulty; -// if (num >= SPECIAL_WAVE_MIN) { -// // TODO: Do we want a special message here? -// for (int i = 0; i < num; i++) -// spawn_wave_monster(g, type); -// g->add_msg( special_wave_message(type->name).c_str() ); -// g->add_msg("********"); -// return; -// } else -// themed_wave = false; // No partially-themed waves -// } -// diff -= type->difficulty; -// spawn_wave_monster(g, type); -// } -// g->add_msg("Welcome to Wave %d!", current_wave); -// g->add_msg("********"); -// } - -// std::vector defense_game::pick_monster_wave(game *g) -// { -// std::vector valid; -// std::vector ret; - -// if (zombies || specials) { -// if (specials) -// valid.push_back(mcat_zombie); -// else -// valid.push_back(mcat_vanilla_zombie); -// } -// if (spiders) -// valid.push_back(mcat_spider); -// if (triffids) -// valid.push_back(mcat_triffid); -// if (robots) -// valid.push_back(mcat_robot); -// if (subspace) -// valid.push_back(mcat_nether); - -// if (valid.empty()) -// debugmsg("Couldn't find a valid monster group for defense!"); -// else -// ret = g->moncats[ valid[rng(0, valid.size() - 1)] ]; - -// return ret; -// } - -// void defense_game::spawn_wave_monster(game *g, mtype *type) -// { -// monster tmp(type); -// if (location == DEFLOC_HOSPITAL || location == DEFLOC_MALL) { -// tmp.posy = SEEY; // Always spawn to the north! -// tmp.posx = rng(SEEX * (MAPSIZE / 2), SEEX * (1 + MAPSIZE / 2)); -// } else if (one_in(2)) { -// tmp.spawn(rng(SEEX * (MAPSIZE / 2), SEEX * (1 + MAPSIZE / 2)), rng(1, SEEY)); -// if (one_in(2)) -// tmp.posy = SEEY * MAPSIZE - 1 - tmp.posy; -// } else { -// tmp.spawn(rng(1, SEEX), rng(SEEY * (MAPSIZE / 2), SEEY * (1 + MAPSIZE / 2))); -// if (one_in(2)) -// tmp.posx = SEEX * MAPSIZE - 1 - tmp.posx; -// } -// tmp.wandx = g->u.posx; -// tmp.wandy = g->u.posy; -// tmp.wandf = 150; -// // We wanna kill! -// tmp.anger = 100; -// tmp.morale = 100; -// g->z.push_back(tmp); -// } - -// std::string defense_game::special_wave_message(std::string name) -// { -// std::stringstream ret; -// ret << "Wave " << current_wave << ": "; -// name[0] += 'A' - 'a'; // Capitalize - -// for (int i = 2; i < name.size(); i++) { -// if (name[i - 1] == ' ') -// name[i] += 'A' - 'a'; -// } - -// switch (rng(1, 6)) { -// case 1: ret << name << " Invasion!"; break; -// case 2: ret << "Attack of the " << name << "s!"; break; -// case 3: ret << name << " Attack!"; break; -// case 4: ret << name << "s from Hell!"; break; -// case 5: ret << "Beware! " << name << "!"; break; -// case 6: ret << "The Day of the " << name << "!"; break; -// case 7: ret << name << " Party!"; break; -// case 8: ret << "Revenge of the " << name << "s!"; break; -// case 9: ret << "Rise of the " << name << "s!"; break; -// } - -// return ret.str(); -// } -// From 4b7038a7a8d9b3eff87ce081474160104ffddde9 Mon Sep 17 00:00:00 2001 From: Whales Date: Sat, 30 Jun 2012 19:31:30 -0400 Subject: [PATCH 30/51] Melee update. Saved NPCs, wild NPCs, interruptable actions, much more. --- Makefile | 4 +- Makefile.Windows | 2 +- action.cpp | 6 +- action.h | 2 + bionics.cpp | 6 + bionics.h | 1 - calendar.cpp | 2 +- catacurse.cpp | 44 +- computer.cpp | 4 +- construction.cpp | 78 +- construction.h | 15 +- crafting.cpp | 21 +- dialogue.h | 41 +- disease.h | 75 +- event.cpp | 12 +- field.cpp | 17 +- game.cpp | 1207 +++++++++++++++++++++++++----- game.h | 22 +- help.cpp | 35 +- inventory.cpp | 11 + inventory.h | 1 + inventory_ui.cpp | 15 +- item.cpp | 101 ++- item.h | 7 + itype.h | 136 +++- itypedef.cpp | 520 +++++++++++-- iuse.cpp | 6 +- keypress.cpp | 15 +- line.cpp | 8 + line.h | 1 + map.cpp | 788 +++++++++++++++++++- map.h | 31 +- mapdata.h | 289 ++++---- mapgen.cpp | 108 ++- mapitemsdef.cpp | 24 +- melee.cpp | 1772 +++++++++++++++++++++++++++++++-------------- mission.cpp | 39 + mission.h | 12 +- mission_start.cpp | 36 + missiondef.cpp | 20 +- monattack.cpp | 60 +- monattack.h | 1 + mongroup.h | 3 + mongroupdef.cpp | 11 +- monmove.cpp | 120 ++- monster.cpp | 54 +- monster.h | 5 +- mtype.h | 6 +- mtypedef.cpp | 63 +- mutation.cpp | 17 +- newcharacter.cpp | 30 +- npc.cpp | 250 +++++-- npc.h | 159 +++- npcmove.cpp | 62 +- npctalk.cpp | 609 ++++++++++++++-- output.cpp | 47 +- output.h | 1 + overmap.cpp | 49 +- overmap.h | 1 + player.cpp | 575 ++++++++++++--- player.h | 64 +- pldata.h | 61 +- ranged.cpp | 41 +- setvector.cpp | 16 + setvector.h | 1 + skill.cpp | 6 + skill.h | 2 +- trap.h | 3 + trapdef.cpp | 4 + trapfunc.cpp | 34 + wish.cpp | 2 +- 71 files changed, 6482 insertions(+), 1409 deletions(-) diff --git a/Makefile b/Makefile index 7ee85c3844..433a78ffda 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ # WARNINGS will spam hundreds of warnings, mostly safe, if turned on # DEBUG is best turned on if you plan to debug in gdb -- please do! # PROFILE is for use with gprof or a similar program -- don't bother generally -#WARNINGS = -Wall -DEBUG = -g +#WARNINGS = -Wall -Wextra -Wno-switch -Wno-sign-compare -Wno-missing-braces -Wno-unused-parameter -Wno-char-subscripts +#DEBUG = -g #PROFILE = -pg ODIR = obj diff --git a/Makefile.Windows b/Makefile.Windows index dc8e0ea1c7..ef50d10d32 100644 --- a/Makefile.Windows +++ b/Makefile.Windows @@ -2,7 +2,7 @@ # WARNINGS will spam hundreds of warnings, mostly safe, if turned on # DEBUG is best turned on if you plan to debug in gdb -- please do! # PROFILE is for use with gprof or a similar program -- don't bother generally -#WARNINGS = -Wall +#WARNINGS = -Wall -Wextra -Wno-switch -Wno-sign-compare -Wno-missing-braces -Wno-unused-parameter -Wno-char-subscripts #DEBUG = -g #PROFILE = -pg diff --git a/action.cpp b/action.cpp index 4cadb8a240..74a6feb6d6 100644 --- a/action.cpp +++ b/action.cpp @@ -105,6 +105,8 @@ action_id look_up_action(std::string ident) return ACTION_READ; if (ident == "wield") return ACTION_WIELD; + if (ident == "pick_style") + return ACTION_PICK_STYLE; if (ident == "reload") return ACTION_RELOAD; if (ident == "unload") @@ -143,6 +145,8 @@ action_id look_up_action(std::string ident) return ACTION_PL_INFO; if (ident == "map") return ACTION_MAP; + if (ident == "missions") + return ACTION_MISSIONS; if (ident == "factions") return ACTION_FACTIONS; if (ident == "morale") @@ -153,7 +157,7 @@ action_id look_up_action(std::string ident) return ACTION_DEBUG; if (ident == "debug_scent") return ACTION_DISPLAY_SCENT; - if (ident == "debug_mon") + if (ident == "debug_mode") return ACTION_TOGGLE_DEBUGMON; return ACTION_NULL; diff --git a/action.h b/action.h index a8cd2b4124..6f80471154 100644 --- a/action.h +++ b/action.h @@ -33,6 +33,7 @@ ACTION_TAKE_OFF, ACTION_EAT, ACTION_READ, ACTION_WIELD, +ACTION_PICK_STYLE, ACTION_RELOAD, ACTION_UNLOAD, ACTION_THROW, @@ -54,6 +55,7 @@ ACTION_QUIT, // Info Screens ACTION_PL_INFO, ACTION_MAP, +ACTION_MISSIONS, ACTION_FACTIONS, ACTION_MORALE, ACTION_HELP, diff --git a/bionics.cpp b/bionics.cpp index 28aaef1af2..2c22f49786 100644 --- a/bionics.cpp +++ b/bionics.cpp @@ -432,6 +432,9 @@ charge mechanism, which must be installed from another CBM."); ch = getch(); while (ch != 'q' && ch != '\n' && ch != KEY_ESCAPE); if (ch == '\n') { + practice(sk_electronics, (100 - chance_of_success) * 1.5); + practice(sk_firstaid, (100 - chance_of_success) * 1.0); + practice(sk_mechanics, (100 - chance_of_success) * 0.5); int success = chance_of_success - rng(1, 100); if (success > 0) { g->add_msg("Successfully installed batteries."); @@ -496,6 +499,9 @@ charge mechanism, which must be installed from another CBM."); } while (ch != '\n' && ch != 'q' && ch != KEY_ESCAPE); if (ch == '\n') { + practice(sk_electronics, (100 - chance_of_success) * 1.5); + practice(sk_firstaid, (100 - chance_of_success) * 1.0); + practice(sk_mechanics, (100 - chance_of_success) * 0.5); bionic_id id = type->options[selection]; int success = chance_of_success - rng(1, 100); if (success > 0) { diff --git a/bionics.h b/bionics.h index ba0f32e18d..b2cf2c6e77 100644 --- a/bionics.h +++ b/bionics.h @@ -23,7 +23,6 @@ bio_ads, bio_ods, bio_scent_mask,bio_scent_vision, bio_cloak, bio_painkiller, bi bio_heatsink, bio_resonator, bio_time_freeze, bio_teleport, bio_blood_anal, bio_blood_filter, bio_alarm, bio_evap, bio_lighter, bio_claws, bio_blaster, bio_laser, bio_emp, -// TODO: fingerhack bio_hydraulics, bio_water_extractor, bio_magnet, bio_fingerhack, bio_lockpick, bio_ground_sonar, max_bio_start, diff --git a/calendar.cpp b/calendar.cpp index aa28eda27c..122ffcacbb 100644 --- a/calendar.cpp +++ b/calendar.cpp @@ -218,7 +218,7 @@ moon_phase calendar::moon() { int phase = day / (DAYS_IN_SEASON / 4); //phase %= 4; Redundant? - if (phase = 3) + if (phase == 3) return MOON_HALF; else return moon_phase(phase); diff --git a/catacurse.cpp b/catacurse.cpp index 25c4855123..ecfac42dbf 100644 --- a/catacurse.cpp +++ b/catacurse.cpp @@ -110,16 +110,16 @@ LRESULT CALLBACK ProcessMessages(HWND__ *hWnd,unsigned int Msg, case WM_KEYDOWN: //Here we handle non-character input switch (wParam){ case VK_LEFT: - lastchar = 52; + lastchar = KEY_LEFT; break; case VK_RIGHT: - lastchar = 54; + lastchar = KEY_RIGHT; break; case VK_UP: - lastchar = 56; + lastchar = KEY_UP; break; case VK_DOWN: - lastchar = 50; + lastchar = KEY_DOWN; break; default: break; @@ -285,8 +285,8 @@ fin.open("data\\FONTDATA"); halfheight=fontheight / 2; WindowWidth=80*fontwidth; WindowHeight=25*fontheight; - WindowX=(GetSystemMetrics(SM_CXSCREEN) / 2)-WindowWidth; //center this - WindowY=(GetSystemMetrics(SM_CYSCREEN) / 2)-WindowHeight; //sucker + WindowX=(GetSystemMetrics(SM_CXSCREEN) / 2)-WindowWidth/2; //center this + WindowY=(GetSystemMetrics(SM_CYSCREEN) / 2)-WindowHeight/2; //sucker WinCreate(); //Create the actual window, register it, etc CheckMessages(); //Let the message queue handle setting up the window WindowDC = GetDC(WindowHandle); @@ -378,17 +378,20 @@ int delwin(WINDOW *win) return 1; }; -inline void newline(WINDOW *win){ -if (win->cursory < win->height - 1) - win->cursory++; -win->cursorx=0; +inline int newline(WINDOW *win){ + if (win->cursory < win->height - 1){ + win->cursory++; + win->cursorx=0; + return 1; + } +return 0; }; inline void addedchar(WINDOW *win){ -win->cursorx++; -win->line[win->cursory].touched=true; -if (win->cursorx > win->width) -newline(win); + win->cursorx++; + win->line[win->cursory].touched=true; + if (win->cursorx > win->width) + newline(win); }; @@ -484,18 +487,19 @@ inline int printstring(WINDOW *win, char *fmt) int size = strlen(fmt); int j; for (j=0; jcursorx <= win->width - 1 && win->cursory <= win->height - 1) { win->line[win->cursory].chars[win->cursorx]=fmt[j]; win->line[win->cursory].FG[win->cursorx]=win->FG; win->line[win->cursory].BG[win->cursorx]=win->BG; win->line[win->cursory].touched=true; addedchar(win); - } - } else if (win->cursory >= win->height - 1) - return 0; - else - newline(win); // if found, make sure to move down a line + } else + return 0; //if we try and write anything outside the window, abort completely +} else // if the character is a newline, make sure to move down a line + if (newline(win)==0){ + return 0; + } } win->draw=true; return 1; diff --git a/computer.cpp b/computer.cpp index 4ce2a94428..67cb3515ae 100644 --- a/computer.cpp +++ b/computer.cpp @@ -574,7 +574,7 @@ INITIATING STANDARD TREMOR TEST..."); case COMPACT_AMIGARA_START: g->add_event(EVENT_AMIGARA, int(g->turn) + 10, 0, 0, 0); if (!g->u.has_artifact_with(AEP_PSYSHIELD)) - g->u.add_disease(DI_AMIGARA, -1, g); + g->u.add_disease(DI_AMIGARA, 20, g); break; case COMPACT_DOWNLOAD_SOFTWARE: @@ -754,7 +754,7 @@ void computer::activate_failure(game *g, computer_failure fail) case COMPFAIL_AMIGARA: g->add_event(EVENT_AMIGARA, int(g->turn) + 5, 0, 0, 0); - g->u.add_disease(DI_AMIGARA, -1, g); + g->u.add_disease(DI_AMIGARA, 20, g); g->explosion(rng(0, SEEX * MAPSIZE), rng(0, SEEY * MAPSIZE), 10, 10, false); g->explosion(rng(0, SEEX * MAPSIZE), rng(0, SEEY * MAPSIZE), 10, 10, false); break; diff --git a/construction.cpp b/construction.cpp index 1e06313c98..edce80eb30 100644 --- a/construction.cpp +++ b/construction.cpp @@ -8,7 +8,6 @@ #include "skill.h" #include "crafting.h" // For the use_comps use_tools functions -#define PICKUP_RANGE 2 bool will_flood_stop(map *m, bool fill[SEEX * MAPSIZE][SEEY * MAPSIZE], int x, int y); @@ -41,22 +40,30 @@ void game::init_construction() * items after a deconstruction. */ - CONSTRUCT("Dig Pit", 0, &construct::able_dig, &construct::done_pit); + CONSTRUCT("Dig Pit", 0, &construct::able_dig, &construct::done_nothing); STAGE(t_pit_shallow, 10); TOOL(itm_shovel, NULL); STAGE(t_pit, 10); TOOL(itm_shovel, NULL); - CONSTRUCT("Spike Pit", 0, &construct::able_pit, &construct::done_trap_pit); + CONSTRUCT("Spike Pit", 0, &construct::able_pit, &construct::done_nothing); STAGE(t_pit_spiked, 5); COMP(itm_spear_wood, 4, NULL); - CONSTRUCT("Fill Pit", 0, &construct::able_pit, &construct::done_fill_pit); + CONSTRUCT("Fill Pit", 0, &construct::able_pit, &construct::done_nothing); STAGE(t_pit_shallow, 5); TOOL(itm_shovel, NULL); STAGE(t_dirt, 5); TOOL(itm_shovel, NULL); + CONSTRUCT("Chop Down Tree", 0, &construct::able_tree, &construct::done_tree); + STAGE(t_dirt, 10); + TOOL(itm_ax, itm_chainsaw_on, NULL); + + CONSTRUCT("Chop Up Log", 0, &construct::able_log, &construct::done_log); + STAGE(t_dirt, 20); + TOOL(itm_ax, itm_chainsaw_on, NULL); + CONSTRUCT("Clean Broken Window", 0, &construct::able_broken_window, &construct::done_nothing); STAGE(t_window_empty, 5); @@ -131,6 +138,10 @@ void game::init_construction() COMP(itm_2x4, 8, NULL); COMP(itm_nail, 40, NULL); + CONSTRUCT("Start vehicle construction", 0, &construct::able_empty, &construct::done_vehicle); + STAGE(t_null, 10); + COMP(itm_frame, 1, NULL); + } void game::construction_menu() @@ -197,7 +208,7 @@ void game::construction_menu() nc_color color_stage = (player_can_build(u, total_inv, current_con, n) ? c_white : c_dkgray); mvwprintz(w_con, posy, 31, color_stage, "Stage %d: %s", n + 1, - terlist[current_con->stages[n].terrain].name.c_str()); + current_con->stages[n].terrain == t_null? "" : terlist[current_con->stages[n].terrain].name.c_str()); posy++; // Print tools construction_stage stage = current_con->stages[n]; @@ -423,8 +434,8 @@ void game::place_construction(constructable *con) max_stage = i; } - u.activity = player_activity(ACT_BUILD, con->stages[starting_stage].time*1000, - con->id); + u.assign_activity(ACT_BUILD, con->stages[starting_stage].time * 1000, con->id); + u.moves = 0; std::vector stages; for (int i = starting_stage; i <= max_stage; i++) @@ -453,7 +464,8 @@ void game::complete_construction() // Make the terrain change int terx = u.activity.placement.x, tery = u.activity.placement.y; - m.ter(terx, tery) = stage.terrain; + if (stage.terrain != t_null) + m.ter(terx, tery) = stage.terrain; construct effects; (effects.*(built->done))(this, point(terx, tery)); @@ -472,6 +484,16 @@ bool construct::able_empty(game *g, point p) return (g->m.move_cost(p.x, p.y) == 2); } +bool construct::able_tree(game *g, point p) +{ + return (g->m.ter(p.x, p.y) == t_tree); +} + +bool construct::able_log(game *g, point p) +{ + return (g->m.ter(p.x, p.y) == t_log); +} + bool construct::able_window(game *g, point p) { return (g->m.ter(p.x, p.y) == t_window_frame || @@ -552,25 +574,43 @@ bool will_flood_stop(map *m, bool fill[SEEX * MAPSIZE][SEEY * MAPSIZE], (skip_west || will_flood_stop(m, fill, x - 1, y )) ); } -void construct::done_pit(game *g, point p) +void construct::done_window_pane(game *g, point p) { - if (g->m.ter(p.x, p.y) == t_pit) - g->m.add_trap(p.x, p.y, tr_pit); + g->m.add_item(g->u.posx, g->u.posy, g->itypes[itm_glass_sheet], 0); } -void construct::done_trap_pit(game *g, point p) +void construct::done_tree(game *g, point p) { - if (g->m.ter(p.x, p.y) == t_pit_spiked) - g->m.add_trap(p.x, p.y, tr_spike_pit); + mvprintz(0, 0, c_red, "Press a direction for the tree to fall in:"); + int x = 0, y = 0; + do + get_direction(g, x, y, input()); + while (x == -2 || y == -2); + x = p.x + x * 3 + rng(-1, 1); + y = p.y + y * 3 + rng(-1, 1); + std::vector tree = line_to(p.x, p.y, x, y, rng(1, 8)); + for (int i = 0; i < tree.size(); i++) { + g->m.destroy(g, tree[i].x, tree[i].y, true); + g->m.ter(tree[i].x, tree[i].y) = t_log; + } } -void construct::done_fill_pit(game *g, point p) +void construct::done_log(game *g, point p) { - if (g->m.tr_at(p.x, p.y) == tr_pit) - g->m.tr_at(p.x, p.y) = tr_null; + int num_sticks = rng(10, 20); + for (int i = 0; i < num_sticks; i++) + g->m.add_item(p.x, p.y, g->itypes[itm_2x4], int(g->turn)); } -void construct::done_window_pane(game *g, point p) +void construct::done_vehicle(game *g, point p) { - g->m.add_item(g->u.posx, g->u.posy, g->itypes[itm_glass_sheet], 0); + std::string name = string_input_popup(20, "Enter new vehicle name"); + vehicle *veh = g->m.add_vehicle (g, veh_custom, p.x, p.y, 270); + if (!veh) + { + debugmsg ("error constructing vehicle"); + return; + } + veh->name = name; + veh->install_part (0, 0, vp_frame_v2); } diff --git a/construction.h b/construction.h index 2041db414d..92b46919ba 100644 --- a/construction.h +++ b/construction.h @@ -35,23 +35,32 @@ struct construct // Construction functions. // Bools - able to build at the given point? bool able_always(game *, point) { return true; } bool able_never (game *, point) { return false; } + bool able_empty (game *, point); // Able if tile is empty + bool able_window(game *, point); // Any window tile bool able_window_pane(game *, point); // Only intact windows bool able_broken_window(game *, point); // Able if tile is broken window + bool able_door(game *, point); // Any door tile bool able_door_broken(game *, point); // Broken door + bool able_wall (game *, point); // Able if tile is wall bool able_wall_wood(game *g, point); // Only player-built walls + bool able_between_walls(game *, point); // Flood-fill contained by walls + bool able_dig(game *, point); // Able if diggable terrain bool able_pit(game *, point); // Able only on pits + bool able_tree(game *, point); // Able on trees + bool able_log(game *, point); // Able on logs + // Does anything special happen when we're finished? void done_nothing(game *, point) { } - void done_pit(game *, point); - void done_trap_pit(game *, point); - void done_fill_pit(game *, point); void done_window_pane(game *, point); + void done_vehicle(game *, point); + void done_tree(game *, point); + void done_log(game *, point); }; diff --git a/crafting.cpp b/crafting.cpp index 4b443c84bd..220516adf1 100644 --- a/crafting.cpp +++ b/crafting.cpp @@ -7,8 +7,6 @@ #include "setvector.h" #include "inventory.h" -#define PICKUP_RANGE 2 - void draw_recipe_tabs(WINDOW *w, craft_cat tab); // This function just defines the recipes used throughout the game. @@ -553,6 +551,23 @@ RECIPE(itm_c4, CC_WEAPON, sk_mechanics, sk_electronics, 4, 8000); TOOL(itm_saw, -1, NULL); COMP(itm_stick, 1, NULL); + RECIPE(itm_frame, CC_MISC, sk_mechanics, sk_null, 1, 8000); + TOOL(itm_welder, 50, NULL); + COMP(itm_steel_lump, 3, NULL); + + RECIPE(itm_steel_plate, CC_MISC, sk_mechanics, sk_null,4, 12000); + TOOL(itm_welder, 100, NULL); + COMP(itm_steel_lump, 8, NULL); + + RECIPE(itm_spiked_plate, CC_MISC, sk_mechanics, sk_null, 4, 12000); + TOOL(itm_welder, 120, NULL); + COMP(itm_steel_lump, 8, NULL); + COMP(itm_steel_chunk, 4, NULL); + + RECIPE(itm_hard_plate, CC_MISC, sk_mechanics, sk_null, 4, 12000); + TOOL(itm_welder, 300, NULL); + COMP(itm_steel_lump, 24, NULL); + RECIPE(itm_crowbar, CC_MISC, sk_mechanics, sk_null, 1, 1000); TOOL(itm_hatchet, -1, itm_hammer, -1, itm_rock, -1, itm_toolset, -1, NULL); COMP(itm_pipe, 1, NULL); @@ -1019,7 +1034,7 @@ void game::pick_recipes(std::vector ¤t, void game::make_craft(recipe *making) { - u.activity = player_activity(ACT_CRAFT, making->time, making->id); + u.assign_activity(ACT_CRAFT, making->time, making->id); u.moves = 0; } diff --git a/dialogue.h b/dialogue.h index d22d339cce..1dd749ffa9 100644 --- a/dialogue.h +++ b/dialogue.h @@ -35,12 +35,27 @@ struct talk_function void mission_failure (game *g, npc *p); void clear_mission (game *g, npc *p); void mission_reward (game *g, npc *p); + void mission_favor (game *g, npc *p); void give_equipment (game *g, npc *p); void start_trade (game *g, npc *p); void follow (game *g, npc *p); // p follows u void deny_follow (game *g, npc *p); // p gets DI_ASKED_TO_FOLLOW + void deny_lead (game *g, npc *p); // p gets DI_ASKED_TO_LEAD + void deny_equipment (game *g, npc *p); // p gets DI_ASKED_FOR_ITEM void enslave (game *g, npc *p) {}; // p becomes slave of u - void hostile (game *g, npc *p) {}; // p turns hostile to u + void hostile (game *g, npc *p); // p turns hostile to u + void flee (game *g, npc *p); + void leave (game *g, npc *p); // p becomes indifferant + + void start_mugging (game *g, npc *p); + void player_leaving (game *g, npc *p); + + void drop_weapon (game *g, npc *p); + void player_weapon_away (game *g, npc *p); + void player_weapon_drop (game *g, npc *p); + + void lead_to_safety (game *g, npc *p); + void start_training (game *g, npc *p); void toggle_use_guns (game *g, npc *p); void toggle_use_grenades (game *g, npc *p); @@ -70,6 +85,8 @@ struct talk_response talk_trial trial; int difficulty; int mission_index; + mission_id miss; // If it generates a new mission + int tempvalue; // Used for various stuff npc_opinion opinion_success; npc_opinion opinion_failure; void (talk_function::*effect_success)(game *, npc *); @@ -83,6 +100,8 @@ struct talk_response trial = TALK_TRIAL_NONE; difficulty = 0; mission_index = -1; + miss = MISSION_NULL; + tempvalue = -1; effect_success = &talk_function::nothing; effect_failure = &talk_function::nothing; success = TALK_NONE; @@ -95,6 +114,8 @@ struct talk_response trial = rhs.trial; difficulty = rhs.difficulty; mission_index = rhs.mission_index; + miss = rhs.miss; + tempvalue = rhs.tempvalue; effect_success = rhs.effect_success; effect_failure = rhs.effect_failure; success = rhs.success; @@ -264,6 +285,20 @@ std::string talk_come_here[10] = { "Look, let's talk!" }; +std::string talk_keep_up[10] = { +"Catch up!", +"Get over here!", +"Catch up, !", +"Keep up!", +"Come on, !", + +"Keep it moving!", +"Stay with me!", +"Keep close!", +"Stay close!", +"Let's keep going!" +}; + std::string talk_wait[10] = { "Hey, where are you?", "Wait up, !", @@ -317,8 +352,7 @@ std::string talk_done_mugging[10] = { "Thanks, !" }; -#define NUM_STATIC_TAGS 23 - +#define NUM_STATIC_TAGS 24 tag_data talk_tags[NUM_STATIC_TAGS] = { {"", &talk_okay}, @@ -339,6 +373,7 @@ tag_data talk_tags[NUM_STATIC_TAGS] = { {"", &talk_hands_up}, {"", &talk_no_faction}, {"", &talk_come_here}, +{"", &talk_keep_up}, {"", &talk_come_here}, {"", &talk_wait}, {"", &talk_let_me_pass}, diff --git a/disease.h b/disease.h index 6c2fc64810..f253369d85 100644 --- a/disease.h +++ b/disease.h @@ -84,6 +84,12 @@ void dis_msg(game *g, dis_type type) case DI_BLIND: g->add_msg("You're blinded!"); break; + case DI_STUNNED: + g->add_msg("You're stunned!"); + break; + case DI_DOWNED: + g->add_msg("You're knocked to the floor!"); + break; case DI_AMIGARA: g->add_msg("You can't look away from the fautline..."); break; @@ -689,6 +695,15 @@ void dis_effect(game *g, player &p, disease &dis) } break; + case DI_ATTACK_BOOST: + case DI_DAMAGE_BOOST: + case DI_DODGE_BOOST: + case DI_ARMOR_BOOST: + case DI_SPEED_BOOST: + if (dis.intensity > 1) + dis.intensity--; + break; + case DI_TELEGLOW: // Default we get around 300 duration points per teleport (possibly more // depending on the source). @@ -726,8 +741,10 @@ void dis_effect(game *g, player &p, disease &dis) g->m.ter(x, y) = t_rubble; beast.spawn(x, y); g->z.push_back(beast); - if (g->u_see(x, y, junk)) + if (g->u_see(x, y, junk)) { + g->cancel_activity_query("A monster appears nearby!"); g->add_msg("A portal opens nearby, and a monster crawls through!"); + } if (one_in(2)) p.rem_disease(DI_TELEGLOW); } @@ -786,8 +803,10 @@ void dis_effect(game *g, player &p, disease &dis) g->m.ter(x, y) = t_rubble; beast.spawn(x, y); g->z.push_back(beast); - if (g->u_see(x, y, junk)) + if (g->u_see(x, y, junk)) { + g->cancel_activity_query("A monster appears nearby!"); g->add_msg("A portal opens nearby, and a monster crawls through!"); + } dis.duration /= 2; } } @@ -879,6 +898,8 @@ std::string dis_name(disease dis) case DI_SLIMED: return "Slimed"; case DI_DEAF: return "Deaf"; case DI_BLIND: return "Blind"; + case DI_STUNNED: return "Stunned"; + case DI_DOWNED: return "Downed"; case DI_POISON: return "Poisoned"; case DI_BADPOISON: return "Badly Poisoned"; case DI_FOODPOISON: return "Food Poisoning"; @@ -909,6 +930,20 @@ std::string dis_name(disease dis) return "Meth Comedown"; case DI_IN_PIT: return "Stuck in Pit"; + + case DI_ATTACK_BOOST: return "Hit Bonus"; + case DI_DAMAGE_BOOST: return "Damage Bonus"; + case DI_DODGE_BOOST: return "Dodge Bonus"; + case DI_ARMOR_BOOST: return "Armor Bonus"; + case DI_SPEED_BOOST: return "Attack Speed Bonus"; + case DI_VIPER_COMBO: + switch (dis.intensity) { + case 1: return "Snakebite Unlocked!"; + case 2: return "Viper Strike Unlocked!"; + default: return "VIPER BUG!!!!"; + } + break; + default: return ""; } } @@ -1029,6 +1064,12 @@ Sounds will not be reported. You cannot talk with NPCs."; case DI_BLIND: return "\ Range of Sight: 0"; + case DI_STUNNED: return "\ +Your movement is randomized."; + + case DI_DOWNED: return "\ +You're knocked to the ground. You have to get up before you can move."; + case DI_POISON: return "\ Perception - 1; Dexterity - 1; Strength - 2 IF not resistant\n\ Occasional pain and/or damage."; @@ -1123,6 +1164,36 @@ Speed -40; Strength - 3; Dexterity - 2; Intelligence - 2"; return "\ You're stuck in a pit. Sight distance is limited and you have to climb out."; + case DI_ATTACK_BOOST: + stream << "To-hit bonus + " << dis.intensity; + return stream.str(); + + case DI_DAMAGE_BOOST: + stream << "Damage bonus + " << dis.intensity; + return stream.str(); + + case DI_DODGE_BOOST: + stream << "Dodge bonus + " << dis.intensity; + return stream.str(); + + case DI_ARMOR_BOOST: + stream << "Armor bonus + " << dis.intensity; + return stream.str(); + + case DI_SPEED_BOOST: + stream << "Attack speed + " << dis.intensity; + return stream.str(); + + case DI_VIPER_COMBO: + switch (dis.intensity) { + case 1: return "\ +Your next strike will be a Snakebite, using your hand in a cone shape. This\n\ +will deal piercing damage."; + case 2: return "\ +Your next strike will be a Viper Strike. It requires both arms to be in good\n\ +condition, and deals massive damage."; + } + default: return "Who knows? This is probably a bug."; } diff --git a/event.cpp b/event.cpp index 6b0dbb15f2..b9a11012ba 100644 --- a/event.cpp +++ b/event.cpp @@ -87,23 +87,25 @@ void event::actualize(game *g) do { tries = 0; if (horizontal) { - monx = rng(0, SEEX * MAPSIZE); + monx = rng(faultx, faultx + 2 * SEEX - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(monx, faulty + n) == t_rock_floor) mony = faulty + n; } } else { // Vertical fault - mony = rng(0, SEEY * MAPSIZE); + mony = rng(faulty, faulty + 2 * SEEY - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(faultx + n, mony) == t_rock_floor) monx = faultx + n; } } tries++; - } while (monx != -1 && mony != -1 && !g->is_empty(monx, mony) && + } while ((monx == -1 || mony == -1 || g->is_empty(monx, mony)) && tries < 10); - horror.spawn(monx, mony); - g->z.push_back(horror); + if (tries < 10) { + horror.spawn(monx, mony); + g->z.push_back(horror); + } } } break; diff --git a/field.cpp b/field.cpp index 3fec764b99..ead61295b3 100644 --- a/field.cpp +++ b/field.cpp @@ -39,6 +39,8 @@ bool map::process_fields_in_submap(game *g, int gridn) if (cur->age == 0) // Don't process "newborn" fields curtype = fd_null; + int part; + vehicle *veh; switch (curtype) { case fd_null: @@ -173,6 +175,10 @@ bool map::process_fields_in_submap(game *g, int gridn) i--; } } + + veh = &(veh_at(x, y, part)); + if (veh->type != veh_null) + veh->damage (part, cur->density * 10, false); // Consume the terrain we're on if (has_flag(explodes, x, y)) { ter(x, y) = ter_id(int(ter(x, y)) + 1); @@ -630,10 +636,13 @@ void map::step_in_field(int x, int y, game *g) break; case fd_flame_burst: - g->add_msg("You're torched by flames!"); - g->u.hit(g, bp_legs, 0, 0, rng(2, 6)); - g->u.hit(g, bp_legs, 1, 0, rng(2, 6)); - g->u.hit(g, bp_torso, 0, 4, rng(4, 9)); + if (!g->u.has_active_bionic(bio_heatsink)) { + g->add_msg("You're torched by flames!"); + g->u.hit(g, bp_legs, 0, 0, rng(2, 6)); + g->u.hit(g, bp_legs, 1, 0, rng(2, 6)); + g->u.hit(g, bp_torso, 0, 4, rng(4, 9)); + } else + g->add_msg("These flames do not burn you."); break; case fd_electricity: diff --git a/game.cpp b/game.cpp index 9993868e6e..8868cfa1a0 100644 --- a/game.cpp +++ b/game.cpp @@ -6,6 +6,7 @@ #include "line.h" #include "computer.h" #include "weather_data.h" +#include "veh_interact.h" #include #include #include @@ -35,6 +36,7 @@ game::game() init_missions(); // Set up mission templates (SEE missiondef.cpp) init_construction(); // Set up constructables (SEE construction.cpp) init_mutations(); + init_vehicles(); // Set up vehicles (SEE veh_typedef.cpp) load_keyboard_settings(); gamemode = new special_game; // Nothing, basically. @@ -486,11 +488,8 @@ void game::create_starting_npcs() tmp.attitude = NPCATT_NULL; tmp.mission = NPC_MISSION_SHELTER; tmp.chatbin.first_topic = TALK_SHELTER; - if (one_in(2)) - tmp.chatbin.missions.push_back(reserve_mission(MISSION_GET_SOFTWARE, tmp.id)); - else - tmp.chatbin.missions.push_back( - reserve_random_mission(ORIGIN_OPENER_NPC, om_location(), tmp.id) ); + tmp.chatbin.missions.push_back( + reserve_random_mission(ORIGIN_OPENER_NPC, om_location(), tmp.id) ); active_npc.push_back(tmp); } @@ -524,6 +523,8 @@ bool game::do_turn() death_screen(); return true; } +// Actual stuff + //build_monmap(); gamemode->per_turn(this); turn.increment(); process_events(); @@ -544,13 +545,16 @@ bool game::do_turn() if ((!u.has_trait(PF_LIGHTEATER) || !one_in(3)) && (!u.has_bionic(bio_recycler) || turn % 300 == 0)) u.hunger++; - if (!u.has_bionic(bio_recycler) || turn % 100 == 0) + if ((!u.has_bionic(bio_recycler) || turn % 100 == 0) && + (!u.has_trait(PF_PLANTSKIN) || !one_in(5))) u.thirst++; u.fatigue++; if (u.fatigue == 192 && !u.has_disease(DI_LYING_DOWN) && !u.has_disease(DI_SLEEP)) { - add_msg("You're feeling tired. Press '$' to lie down for sleep."); - u.activity.type = ACT_NULL; + if (u.activity.type == ACT_NULL) + add_msg("You're feeling tired. Press '$' to lie down for sleep."); + else + cancel_activity_query("You're feeling tired."); } if (u.stim < 0) u.stim++; @@ -594,8 +598,8 @@ bool game::do_turn() spawn_mon(-1 + 2 * rng(0, 1), -1 + 2 * rng(0, 1)); nextspawn = turn; } - process_activity(); + process_activity(); while (u.moves > 0) { if (!u.has_disease(DI_SLEEP) && u.activity.type == ACT_NULL) @@ -610,6 +614,7 @@ bool game::do_turn() } } update_scent(); + m.vehmove(this); m.process_fields(this); m.process_active_items(this); m.step_in_field(u.posx, u.posy, this); @@ -693,7 +698,7 @@ void game::process_activity() draw(); if (u.activity.type == ACT_WAIT) { // Based on time, not speed u.activity.moves_left -= 100; - u.pause(); + u.pause(this); } else { u.activity.moves_left -= u.moves; u.moves = 0; @@ -761,15 +766,46 @@ void game::process_activity() case ACT_BUILD: complete_construction(); break; + + case ACT_TRAIN: + if (u.activity.index < 0) { + add_msg("You learn %s.", itypes[0 - u.activity.index]->name.c_str()); + u.styles.push_back( itype_id(0 - u.activity.index) ); + } else { + u.sklevel[ u.activity.index ]++; + add_msg("You finish training %s to level %d.", + skill_name(u.activity.index).c_str(), u.sklevel[u.activity.index]); + } + break; + + case ACT_VEHICLE: + complete_vehicle (this); + break; } + + bool act_veh = (u.activity.type == ACT_VEHICLE); u.activity.type = ACT_NULL; + if (act_veh) { + if (u.activity.values.size() < 7) + debugmsg ("process_activity invalid ACT_VEHICLE values:%d", + u.activity.values.size()); + else { + vehicle &veh = m.veh_at(u.activity.values[0], u.activity.values[1]); + if (veh.type != veh_null) { + exam_vehicle(veh, u.activity.values[0], u.activity.values[1], + u.activity.values[2], u.activity.values[3]); + return; + } else + debugmsg ("process_activity ACT_VEHICLE: vehicle not found"); + } + } } } } void game::cancel_activity() { - u.activity.type = ACT_NULL; + u.cancel_activity(); } void game::cancel_activity_query(const char* message, ...) @@ -781,30 +817,38 @@ void game::cancel_activity_query(const char* message, ...) va_end(ap); std::string s(buff); + bool doit = false;; + switch (u.activity.type) { + case ACT_NULL: + doit = false; + break; case ACT_READ: if (query_yn("%s Stop reading?", s.c_str())) - u.activity.type = ACT_NULL; + doit = true; break; case ACT_RELOAD: if (query_yn("%s Stop reloading?", s.c_str())) - u.activity.type = ACT_NULL; + doit = true; break; case ACT_CRAFT: if (query_yn("%s Stop crafting?", s.c_str())) - u.activity.type = ACT_NULL; + doit = true; break; case ACT_BUTCHER: if (query_yn("%s Stop butchering?", s.c_str())) - u.activity.type = ACT_NULL; + doit = true; break; case ACT_BUILD: if (query_yn("%s Stop construction?", s.c_str())) - u.activity.type = ACT_NULL; + doit = true; break; default: - u.activity.type = ACT_NULL; + doit = true; } + + if (doit) + u.cancel_activity(); } void game::update_weather() @@ -825,6 +869,7 @@ void game::update_weather() } } int choice = rng(0, total - 1); + weather_type old_weather = weather; weather_type new_weather = WEATHER_CLEAR; while (choice >= chances[new_weather]) { choice -= chances[new_weather]; @@ -838,8 +883,8 @@ void game::update_weather() if (weather == WEATHER_SUNNY && turn.is_night()) weather = WEATHER_CLEAR; - if (weather_data[weather].dangerous && levz >= 0 && - m.is_outside(u.posx, u.posy)) { + if (weather != old_weather && weather_data[weather].dangerous && + levz >= 0 && m.is_outside(u.posx, u.posy)) { std::stringstream weather_text; weather_text << "The weather changed to " << weather_data[weather].name << "!"; @@ -872,8 +917,12 @@ int game::assign_mission_id() void game::give_mission(mission_id type) { mission tmp = mission_types[type].create(this); + active_missions.push_back(tmp); u.active_missions.push_back(tmp.uid); u.active_mission = u.active_missions.size() - 1; + mission_start m_s; + mission *miss = find_mission(tmp.uid); + (m_s.*miss->type->start)(this, miss); } void game::assign_mission(int id) @@ -906,10 +955,8 @@ int game::reserve_random_mission(mission_origin origin, point p, int npc_id) } } - if (valid.empty()) { - debugmsg("No missions with origin %d found.", origin); + if (valid.empty()) return -1; - } int index = valid[rng(0, valid.size() - 1)]; @@ -1081,53 +1128,83 @@ void game::get_input() gamemode->pre_action(this, act); + int veh_part; + vehicle &veh = m.veh_at (u.posx, u.posy, veh_part); + bool veh_ctrl = veh.type != veh_null && veh.player_in_control (&u); + switch (act) { case ACTION_PAUSE: if (run_mode == 2) // Monsters around and we don't wanna pause add_msg("Monster spotted--safe mode is on! (Press '!' to turn it off.)"); else - u.pause(); + u.pause(this); break; case ACTION_MOVE_N: - plmove(0, -1); + if (u.in_vehicle) + pldrive(0, -1); + else + plmove(0, -1); break; case ACTION_MOVE_NE: - plmove(1, -1); + if (u.in_vehicle) + pldrive(1, -1); + else + plmove(1, -1); break; case ACTION_MOVE_E: - plmove(1, 0); + if (u.in_vehicle) + pldrive(1, 0); + else + plmove(1, 0); break; case ACTION_MOVE_SE: - plmove(1, 1); + if (u.in_vehicle) + pldrive(1, 1); + else + plmove(1, 1); break; case ACTION_MOVE_S: + if (u.in_vehicle) + pldrive(0, 1); + else plmove(0, 1); break; case ACTION_MOVE_SW: - plmove(-1, 1); + if (u.in_vehicle) + pldrive(-1, 1); + else + plmove(-1, 1); break; case ACTION_MOVE_W: - plmove(-1, 0); + if (u.in_vehicle) + pldrive(-1, 0); + else + plmove(-1, 0); break; case ACTION_MOVE_NW: - plmove(-1, -1); + if (u.in_vehicle) + pldrive(-1, -1); + else + plmove(-1, -1); break; case ACTION_MOVE_DOWN: - vertical_move(-1, false); + if (!u.in_vehicle) + vertical_move(-1, false); break; case ACTION_MOVE_UP: - vertical_move( 1, false); + if (!u.in_vehicle) + vertical_move( 1, false); break; case ACTION_OPEN: @@ -1139,7 +1216,10 @@ void game::get_input() break; case ACTION_SMASH: - smash(); + if (veh_ctrl) + handbrake(); + else + smash(); break; case ACTION_EXAMINE: @@ -1201,6 +1281,15 @@ void game::get_input() wield(); break; + case ACTION_PICK_STYLE: + u.pick_style(this); + if (u.weapon.type->id == 0 || u.weapon.is_style()) { + u.weapon = item(itypes[u.style_selected], 0); + u.weapon.invlet = ':'; + } + refresh_all(); + break; + case ACTION_RELOAD: reload(); break; @@ -1235,7 +1324,14 @@ void game::get_input() break; case ACTION_WAIT: - wait(); + if (veh_ctrl) + { + veh.turret_mode++; + if (veh.turret_mode > 1) + veh.turret_mode = 0; + } + else + wait(); break; case ACTION_CRAFT: @@ -1243,11 +1339,17 @@ void game::get_input() break; case ACTION_CONSTRUCT: - construction_menu(); + if (u.in_vehicle) + add_msg("You can't construct while in vehicle."); + else + construction_menu(); break; case ACTION_SLEEP: - if (query_yn("Are you sure you want to sleep?")) { + if (veh_ctrl) { + veh.cruise_on = !veh.cruise_on; + add_msg("Cruise control turned %s.", veh.cruise_on? "on" : "off"); + } else if (query_yn("Are you sure you want to sleep?")) { u.try_to_sleep(this); u.moves = 0; } @@ -1316,6 +1418,10 @@ void game::get_input() draw_overmap(); break; + case ACTION_MISSIONS: + list_missions(); + break; + case ACTION_FACTIONS: list_factions(); break; @@ -1459,20 +1565,70 @@ bool game::load_master() { std::ifstream fin; std::string data; + char junk; fin.open("save/master.gsav"); if (!fin.is_open()) return false; - char datatype; - while (!fin.eof()) { - fin >> datatype; - if (datatype == 'F') { - getline(fin, data); - faction tmp; - tmp.load_info(data); - factions.push_back(tmp); +// First, get the next ID numbers for each of these + fin >> next_mission_id >> next_faction_id >> next_npc_id; + int num_missions, num_npc, num_factions, num_items; + + fin >> num_missions; + debugmsg("missions: %d", num_missions); + if (fin.peek() == '\n') + fin.get(junk); // Chomp that pesky endline + for (int i = 0; i < num_missions; i++) { + mission tmpmiss; + tmpmiss.load_info(this, fin); + active_missions.push_back(tmpmiss); + } + + fin >> num_factions; + debugmsg("factions: %d", num_factions); + if (fin.peek() == '\n') + fin.get(junk); // Chomp that pesky endline + for (int i = 0; i < num_factions; i++) { + getline(fin, data); + faction tmp; + tmp.load_info(data); + factions.push_back(tmp); + } +// NPCs come next + fin >> num_npc; + debugmsg("npcs: %d", num_npc); + if (fin.peek() == '\n') + fin.get(junk); // Chomp that pesky endline + for (int i = 0; i < num_npc; i++) { + getline(fin, data); + npc tmp; + tmp.load_info(this, data); +// We need to load up all their items too + fin >> num_items; + std::vector tmpinv; + for (int j = 0; j < num_items; j++) { + std::string itemdata; + char item_place; + fin >> item_place; + if (!fin.eof()) { + getline(fin, itemdata); + if (item_place == 'I') + tmpinv.push_back(item(itemdata, this)); + else if (item_place == 'C' && !tmpinv.empty()) + tmpinv[tmpinv.size() - 1].contents.push_back(item(itemdata, this)); + else if (item_place == 'W') + tmp.worn.push_back(item(itemdata, this)); + else if (item_place == 'w') + tmp.weapon = item(itemdata, this); + else if (item_place == 'c') + tmp.weapon.contents.push_back(item(itemdata, this)); + } } + active_npc.push_back(tmp); + if (fin.peek() == '\n') + fin.get(junk); // Chomp that pesky endline } + fin.close(); return true; } @@ -1540,14 +1696,18 @@ void game::load(std::string name) // And the player's inventory... char item_place; std::string itemdata; +// We need a temporary vector of items. Otherwise, when we encounter an item +// which is contained in another item, the auto-sort/stacking behavior of the +// player's inventory may cause the contained item to be misplaced. + std::vector tmpinv; while (!fin.eof()) { fin >> item_place; if (!fin.eof()) { getline(fin, itemdata); if (item_place == 'I') - u.inv.push_back(item(itemdata, this)); + tmpinv.push_back(item(itemdata, this)); else if (item_place == 'C') - u.inv[u.inv.size() - 1].contents.push_back(item(itemdata, this)); + tmpinv[tmpinv.size() - 1].contents.push_back(item(itemdata, this)); else if (item_place == 'W') u.worn.push_back(item(itemdata, this)); else if (item_place == 'w') @@ -1556,6 +1716,8 @@ void game::load(std::string name) u.weapon.contents.push_back(item(itemdata, this)); } } +// Now dump tmpinv into the player's inventory + u.inv.add_stack(tmpinv); fin.close(); // Now load up the master game data; factions (and more?) load_master(); @@ -1592,11 +1754,25 @@ void game::save() fout << u.save_info() << std::endl; fout << std::endl; fout.close(); + // Now write things that aren't player-specific: factions and NPCs fout.open(masterfile.str().c_str()); + + fout << next_mission_id << " " << next_faction_id << " " << next_npc_id << + " " << active_missions.size() << " "; + for (int i = 0; i < active_missions.size(); i++) + fout << active_missions[i].save_info() << " "; + + fout << factions.size() << std::endl; for (int i = 0; i < factions.size(); i++) - fout << "F " << factions[i].save_info() << std::endl; + fout << factions[i].save_info() << std::endl; + + fout << active_npc.size() << std::endl; + for (int i = 0; i < active_npc.size(); i++) + fout << active_npc[i].save_info() << std::endl; + fout.close(); + // Finally, save artifacts. if (itypes.size() > num_all_items) { fout.open("save/artifacts.gsav"); @@ -1626,7 +1802,7 @@ void game::decrease_nextinv() else if (nextinv == 'A') nextinv = 'z'; else - nextinv++; + nextinv--; } void game::add_msg(const char* msg, ...) @@ -1683,8 +1859,13 @@ void game::debug() "Check game state...", // 7 "Kill NPCs", // 8 "Mutate", // 9 -// "Cancel", // 9 + "Spawn a vehicle", // 10 + "Increase all skills", // 11 + "Learn all melee styles", // 12 + "Cancel", // 13 NULL); + int veh_num; + std::vector opts; switch (action) { case 1: wish(); @@ -1751,6 +1932,29 @@ int(turn), int(nextspawn), z.size(), events.size()); mutation_wish(); break; + case 10: + if (m.veh_at(u.posx, u.posy).type != veh_null) { + debugmsg ("There's already vehicle here"); + break; + } + for (int i = 2; i < vtypes.size(); i++) + opts.push_back (vtypes[i]->name); + opts.push_back (std::string("Cancel")); + veh_num = menu_vec ("Choose vehicle to spawn", opts) + 1; + if (veh_num > 1 && veh_num < num_vehicles) + m.add_vehicle (this, (vhtype_id)veh_num, u.posx, u.posy, -90); + break; + + case 11: + for (int i = 0; i < num_skill_types; i++) + u.sklevel[i]++; + break; + + case 12: + for (int i = itm_style_karate; i <= itm_style_zui_quan; i++) + u.styles.push_back( itype_id(i) ); + break; + } erase(); refresh_all(); @@ -2145,12 +2349,12 @@ void game::draw_ter() if (scent(realx, realy) != 0) { int tempx = u.posx - realx, tempy = u.posy - realy; if (!(isBetween(tempx, -2, 2) && isBetween(tempy, -2, 2))) { - mvwputch(w_terrain, realy + SEEY - u.posy, - realx + SEEX - u.posx, c_magenta, '#'); if (mon_at(realx, realy) != -1) - mvwputch(w_terrain, realy + SEEY - u.posy, - realx + SEEX - u.posx, c_white, '?'); - + mvwputch(w_terrain, realy + SEEY - u.posy, realx + SEEX - u.posx, + c_white, '?'); + else + mvwputch(w_terrain, realy + SEEY - u.posy, realx + SEEX - u.posx, + c_magenta, '#'); } } } @@ -2712,6 +2916,7 @@ void game::monmove() z.erase(z.begin() + i);// Delete us if no replacement found dead = true; } + //build_monmap(); } if (!dead) { @@ -2726,6 +2931,7 @@ void game::monmove() z[i].made_footstep = false; z[i].plan(this); // Formulate a path to follow z[i].move(this); // Move one square, possibly hit u + //build_monmap(); z[i].process_triggers(this); m.mon_in_field(z[i].posx, z[i].posy, this, &(z[i])); if (z[i].hurt(0)) { // Maybe we died... @@ -2741,7 +2947,7 @@ void game::monmove() rl_dist(u.posx, u.posy, z[i].posx, z[i].posy) <= 5) { u.power_level--; add_msg("Your motion alarm goes off!"); - u.activity.type = ACT_NULL; + cancel_activity_query("Your motion alarm goes off!"); if (u.has_disease(DI_SLEEP) || u.has_disease(DI_LYING_DOWN)) { u.rem_disease(DI_SLEEP); u.rem_disease(DI_LYING_DOWN); @@ -2760,6 +2966,7 @@ void game::monmove() levx, levy, 1, 1)); } z.erase(z.begin()+i); + //build_monmap(); i--; } else z[i].receive_moves(); @@ -2779,6 +2986,7 @@ void game::monmove() while (active_npc[i].moves > 0 && turns < 10) { turns++; active_npc[i].move(this); + //build_monmap(); } if (turns == 10) { add_msg("%s's brain explodes!", active_npc[i].name.c_str()); @@ -3020,12 +3228,19 @@ void game::explosion(int x, int y, int power, int shrapnel, bool fire) m.destroy(this, i, j, false); int mon_hit = mon_at(i, j), npc_hit = npc_at(i, j); - if (mon_hit != -1 && z[mon_hit].hurt(rng(dam / 2, dam * 1.5))) { + if (mon_hit != -1 && !z[mon_hit].dead && + z[mon_hit].hurt(rng(dam / 2, dam * 1.5))) { if (z[mon_hit].hp < 0 - 1.5 * z[mon_hit].type->hp) explode_mon(mon_hit); // Explode them if it was big overkill else kill_mon(mon_hit); + + int vpart; + vehicle &veh = m.veh_at(i, j, vpart); + if (veh.type != veh_null) + veh.damage (vpart, dam, false); } + if (npc_hit != -1) { active_npc[npc_hit].hit(this, bp_torso, 0, rng(dam / 2, dam * 1.5), 0); active_npc[npc_hit].hit(this, bp_head, 0, rng(dam / 3, dam), 0); @@ -3305,18 +3520,38 @@ int game::npc_at(int x, int y) return -1; } +/* +void game::build_monmap() +{ + for (int x = 0; x < SEEX * MAPSIZE; x++) { + for (int y = 0; y < SEEY * MAPSIZE; y++) + monmap[x][y] = -1; + } + for (int i = 0; i < z.size(); i++) + monmap[ z[i].posx ][ z[i].posy ] = i; +} +*/ + int game::mon_at(int x, int y) { +/* + if (monmap[x][y] != -2) + return monmap[x][y]; +*/ for (int i = 0; i < z.size(); i++) { - if (z[i].posx == x && z[i].posy == y) + if (z[i].posx == x && z[i].posy == y) { + //monmap[x][y] = i; return i; + } } + //monmap[x][y] = -1; return -1; } bool game::is_empty(int x, int y) { - return (m.move_cost(x, y) > 0 && npc_at(x, y) == -1 && mon_at(x, y) == -1 && + return ((m.move_cost(x, y) > 0 || m.has_flag(liquid, x, y)) && + npc_at(x, y) == -1 && mon_at(x, y) == -1 && (u.posx != x || u.posy != y)); } @@ -3436,10 +3671,29 @@ void game::open() last_action += ch; get_direction(this, openx, openy, ch); if (openx != -2 && openy != -2) + { + int vpart; + vehicle &veh = m.veh_at(u.posx + openx, u.posy + openy, vpart); + if (veh.type != veh_null && veh.part_flag(vpart, vpf_openable)) + { + if (veh.parts[vpart].open) + { + add_msg("That door is already open."); + u.moves += 100; + } + else + { + veh.parts[vpart].open = 1; + veh.insides_dirty = true; + } + return; + } + if (m.ter(u.posx, u.posy) == t_floor) didit = m.open_door(u.posx + openx, u.posy + openy, true); else didit = m.open_door(u.posx + openx, u.posy + openy, false); + } else add_msg("Invalid direction."); if (!didit) { @@ -3471,8 +3725,15 @@ void game::close() if (closex != -2 && closey != -2) { closex += u.posx; closey += u.posy; + int vpart; + vehicle &veh = m.veh_at(closex, closey, vpart); if (mon_at(closex, closey) != -1) add_msg("There's a %s in the way!",z[mon_at(closex, closey)].name().c_str()); + else if (veh.type != veh_null && veh.part_flag(vpart, vpf_openable) && veh.parts[vpart].open) { + veh.parts[vpart].open = 0; + veh.insides_dirty = true; + didit = true; + } else if (m.i_at(closex, closey).size() > 0) add_msg("There's %s in the way!", m.i_at(closex, closey).size() == 1 ? m.i_at(closex, closey)[0].tname(this).c_str() : "some stuff"); @@ -3544,8 +3805,221 @@ void game::use_item() u.use(this, ch); } +bool game::pl_choose_vehicle (int &x, int &y) +{ + refresh_all(); + mvprintz(0, 0, c_red, "Choose a vehicle at direction:"); + int dirx, diry; + get_direction(dirx, diry, input()); + if (dirx == -2) { + add_msg("Invalid direction!"); + return false; + } + x += dirx; + y += diry; + return true; +/* +int junk; + int range = 3; + int x0 = x - range; + int y0 = y - range; + int x1 = x + range; + int y1 = y + range; + for (int j = x - SEEX; j <= x + SEEX; j++) { + for (int k = y - SEEY; k <= y + SEEY; k++) { + if (u_see(j, k, junk)) { + if (k >= y0 && k <= y1 && j >= x0 && j <= x1) + m.drawsq(w_terrain, u, j, k, false, true); + else + mvwputch(w_terrain, k + SEEY - y, j + SEEX - x, c_dkgray, '#'); + } + } + } + + // target() sets x and y, and returns an empty vector if we canceled (Esc) + std::vector trajectory = + target(x, y, x0, y0, x1, y1, std::vector (), junk, 0); + return trajectory.size() > 0; +*/ +} + +bool game::vehicle_near () +{ + for (int dx = -1; dx <= 1; dx++) + for (int dy = -1; dy <= 1; dy++) + { + vehicle &veh = m.veh_at (u.posx + dx, u.posy + dy); + if (veh.type != veh_null) + return true; + } + return false; +} + +bool game::pl_refill_vehicle (vehicle &veh, int part, bool test) +{ + if (!veh.part_flag(part, vpf_fuel_tank)) + return false; + int i_itm = -1; + item *p_itm = 0; + int min_charges = -1; + bool i_cont = false; + + int ftype = veh.part_info(part).fuel_type; + itype_id itid = default_ammo((ammotype)ftype); + if (u.weapon.is_container() && u.weapon.contents.size() > 0 && u.weapon.contents[0].type->id == itid) + { + i_itm = -2; + p_itm = &u.weapon.contents[0]; + min_charges = u.weapon.contents[0].charges; + i_cont = true; + } + else + if (u.weapon.type->id == itid) + { + i_itm = -2; + p_itm = &u.weapon; + min_charges = u.weapon.charges; + } + else + for (int i = 0; i < u.inv.size(); i++) + { + item *itm = &u.inv[i]; + bool cont = false; + if (itm->is_container() && itm->contents.size() > 0) + { + cont = true; + itm = &(itm->contents[0]); + } + if (itm->type->id != itid) + continue; + if (i_itm < 0 || min_charges > itm->charges) + { + i_itm = i; + p_itm = itm; + i_cont = cont; + min_charges = itm->charges; + } + } + if (i_itm == -1) + return false; + else + if (test) + return true; + + int fuel_per_charge = 1; + switch (ftype) + { + case AT_PLUT: + fuel_per_charge = 1000; + break; + case AT_PLASMA: + fuel_per_charge = 100; + break; + default:; + } + int max_fuel = veh.part_info(part).size; + int dch = (max_fuel - veh.parts[part].amount) / fuel_per_charge; + if (dch < 1) + dch = 1; + bool rem_itm = min_charges <= dch; + int used_charges = rem_itm? min_charges : dch; + veh.parts[part].amount += used_charges * fuel_per_charge; + if (veh.parts[part].amount > max_fuel) + veh.parts[part].amount = max_fuel; + + add_msg ("You %s %s's %s%s.", ftype == AT_BATT? "recharge" : "refill", veh.name.c_str(), + ftype == AT_BATT? "battery" : (ftype == AT_PLUT? "reactor" : "fuel tank"), + veh.parts[part].amount == max_fuel? " to its maximum" : ""); + + p_itm->charges -= used_charges; + if (rem_itm) + { + if (i_itm == -2) + { + if (i_cont) + u.weapon.contents.erase (u.weapon.contents.begin()); + else + u.remove_weapon (); + } + else + { + if (i_cont) + u.inv[i_itm].contents.erase (u.inv[i_itm].contents.begin()); + else + u.inv.remove_item (i_itm); + } + } +} + +void game::handbrake () +{ + vehicle &veh = m.veh_at (u.posx, u.posy); + if (veh.type == veh_null) + return; + add_msg ("You pull a handbrake."); + veh.cruise_velocity = 0; + if (veh.last_turn != 0 && rng (15, 60) * 100 < abs(veh.velocity)) + { + veh.skidding = true; + add_msg ("You lose control of %s.", veh.name.c_str()); + veh.turn (veh.last_turn > 0? 60 : -60); + } + else + if (veh.velocity < 0) + veh.stop(); + else + { + veh.velocity = veh.velocity / 2 - 10*100; + if (veh.velocity < 0) + veh.stop(); + } + u.moves = 0; +} + +void game::exam_vehicle(vehicle &veh, int examx, int examy, int cx, int cy) +{ + veh_interact vehint; + vehint.cx = cx; + vehint.cy = cy; + vehint.exec(this, &veh, examx, examy); +// debugmsg ("exam_vehicle cmd=%c %d", vehint.sel_cmd, (int) vehint.sel_cmd); + if (vehint.sel_cmd != ' ') + { // TODO: different activity times + u.activity = player_activity(ACT_VEHICLE, + vehint.sel_cmd == 'f'? 200 : 20000, + (int) vehint.sel_cmd); + u.activity.values.push_back (veh.global_x()); // values[0] + u.activity.values.push_back (veh.global_y()); // values[1] + u.activity.values.push_back (vehint.cx); // values[2] + u.activity.values.push_back (vehint.cy); // values[3] + u.activity.values.push_back (-vehint.ddx - vehint.cy); // values[4] + u.activity.values.push_back (vehint.cx - vehint.ddy); // values[5] + u.activity.values.push_back (vehint.sel_part); // values[6] + u.moves = 0; + } + refresh_all(); +} + void game::examine() { + if (u.in_vehicle) + { + int vpart; + vehicle &veh = m.veh_at (u.posx, u.posy, vpart); + bool qexv = (veh.type != veh_null && veh.velocity != 0? + query_yn("Really exit moving vehicle?") : query_yn("Exit vehicle?")); + if (qexv) + { + m.unboard_vehicle (this, u.posx, u.posy); + u.moves -= 200; + if (veh.velocity) // TODO: move player out of harms way + { + int dsgn = veh.parts[vpart].mount_dx > 0? 1 : -1; + fling_player_or_monster (&u, 0, veh.face.dir() + 90 * dsgn, 35); + } + return; + } + } mvwprintw(w_terrain, 0, 0, "Examine where? (Direction button) "); wrefresh(w_terrain); int examx, examy; @@ -3561,6 +4035,24 @@ void game::examine() examx += u.posx; examy += u.posy; add_msg("That is a %s.", m.tername(examx, examy).c_str()); + + int veh_part = 0; + vehicle &veh = m.veh_at (examx, examy, veh_part); + if (veh.type != veh_null) + { + int vpcargo = veh.part_with_feature(veh_part, vpf_cargo, false); + if (vpcargo >= 0 && veh.parts[vpcargo].items.size() > 0) + pickup(examx, examy, 0); + else + if (u.in_vehicle) + add_msg ("You can't do that while onboard."); + else + if (abs(veh.velocity) > 0) + add_msg ("You can't do that on moving vehicle."); + else + exam_vehicle (veh, examx, examy); + } + else if (m.has_flag(sealed, examx, examy)) { if (m.trans(examx, examy)) { std::string buff; @@ -3849,6 +4341,8 @@ point game::look_around() for (int j = 1; j < 46; j++) mvwputch(w_look, i, j, c_white, ' '); } + int veh_part = 0; + vehicle &veh = m.veh_at(lx, ly, veh_part); if (u_see(lx, ly, junk)) { if (m.move_cost(lx, ly) == 0) mvwprintw(w_look, 1, 1, "%s; Impassable", m.tername(lx, ly).c_str()); @@ -3864,6 +4358,7 @@ point game::look_around() u.per_cur - u.encumb(bp_eyes) >= traps[m.tr_at(lx, ly)]->visibility) mvwprintz(w_look, 5, 1, traps[m.tr_at(lx, ly)]->color, "%s", traps[m.tr_at(lx, ly)]->name.c_str()); + int dex = mon_at(lx, ly); if (dex != -1 && u_see(&(z[dex]), junk)) { z[mon_at(lx, ly)].draw(w_terrain, u.posx, u.posy, true); @@ -3879,6 +4374,10 @@ point game::look_around() mvwprintw(w_look, 3, 1, "There are several items there."); else if (m.i_at(lx, ly).size() == 1) mvwprintw(w_look, 3, 1, "There is an item there."); + } else if (veh.type != veh_null) { + mvwprintw(w_look, 3, 1, "There is a %s there. Parts:", veh.name.c_str()); + veh.print_part_desc(w_look, 4, 48, veh_part); + m.drawsq(w_terrain, u, lx, ly, true, true); } else if (m.i_at(lx, ly).size() > 0) { mvwprintw(w_look, 3, 1, "There is a %s there.", m.i_at(lx, ly)[0].tname(this).c_str()); @@ -3891,6 +4390,12 @@ point game::look_around() } else if (lx == u.posx && ly == u.posy) { mvwputch_inv(w_terrain, SEEX, SEEY, u.color(), '@'); mvwprintw(w_look, 1, 1, "You (%s)", u.name.c_str()); + if (veh.type != veh_null) { + mvwprintw(w_look, 3, 1, "There is a %s there. Parts:", veh.name.c_str()); + veh.print_part_desc(w_look, 4, 48, veh_part); + m.drawsq(w_terrain, u, lx, ly, true, true); + } + } else { mvwputch(w_terrain, ly - u.posy + SEEY, lx - u.posx + SEEX, c_white, 'x'); mvwprintw(w_look, 1, 1, "Unseen."); @@ -3913,8 +4418,16 @@ void game::pickup(int posx, int posy, int min) } bool weight_is_okay = (u.weight_carried() <= u.weight_capacity() * .25); bool volume_is_okay = (u.volume_carried() <= u.volume_capacity() - 2); + int veh_part = 0; + vehicle &veh = m.veh_at (posx, posy, veh_part); + veh_part = veh.part_with_feature(veh_part, vpf_cargo, false); + bool from_veh = veh.type != veh_null && veh_part >= 0 && + veh.parts[veh_part].items.size() > 0; + if (from_veh) + if (!query_yn("Get items from %s?", veh.part_info(veh_part).name)) + from_veh = false; // Picking up water? - if (m.i_at(posx, posy).size() == 0) { + if ((!from_veh) && m.i_at(posx, posy).size() == 0) { if (m.has_flag(swimmable, posx, posy) || m.ter(posx, posy) == t_toilet) { item water = m.water_from(posx, posy); if (query_yn("Drink from your hands?")) { @@ -3928,9 +4441,10 @@ void game::pickup(int posx, int posy, int min) } return; // Few item here, just get it - } else if (m.i_at(posx, posy).size() <= min) { + } else if ((from_veh? veh.parts[veh_part].items.size() : + m.i_at(posx, posy).size() ) <= min) { int iter = 0; - item newit = m.i_at(posx, posy)[0]; + item newit = from_veh? veh.parts[veh_part].items[0] : m.i_at(posx, posy)[0]; if (newit.made_of(LIQUID)) { add_msg("You can't pick up a liquid!"); return; @@ -3953,10 +4467,13 @@ void game::pickup(int posx, int posy, int min) decrease_nextinv(); } else if (u.volume_carried() + newit.volume() > u.volume_capacity()) { if (u.is_armed()) { - if (u.weapon.type->id < num_items && // Not a bionic + if (!u.weapon.has_flag(IF_NO_UNWIELD) && // Not a bionic query_yn("Drop your %s and pick up %s?", u.weapon.tname(this).c_str(), newit.tname(this).c_str())) { - m.i_clear(posx, posy); + if (from_veh) + veh.remove_item (veh_part, 0); + else + m.i_clear(posx, posy); m.add_item(posx, posy, u.remove_weapon()); u.i_add(newit); u.wield(this, u.inv.size() - 1); @@ -3967,20 +4484,29 @@ void game::pickup(int posx, int posy, int min) } else { u.i_add(newit); u.wield(this, u.inv.size() - 1); - m.i_clear(posx, posy); + if (from_veh) + veh.remove_item (veh_part, 0); + else + m.i_clear(posx, posy); u.moves -= 100; add_msg("Wielding %c - %s", newit.invlet, newit.tname(this).c_str()); } } else if (!u.is_armed() && (u.volume_carried() + newit.volume() > u.volume_capacity() - 2 || - newit.is_weap())) { + newit.is_weap() || newit.is_gun())) { u.weapon = newit; - m.i_clear(posx, posy); + if (from_veh) + veh.remove_item (veh_part, 0); + else + m.i_clear(posx, posy); u.moves -= 100; add_msg("Wielding %c - %s", newit.invlet, newit.tname(this).c_str()); } else { u.i_add(newit); - m.i_clear(posx, posy); + if (from_veh) + veh.remove_item (veh_part, 0); + else + m.i_clear(posx, posy); u.moves -= 100; add_msg("%c - %s", newit.invlet, newit.tname(this).c_str()); } @@ -3995,7 +4521,7 @@ void game::pickup(int posx, int posy, int min) WINDOW* w_pickup = newwin(12, 48, 0, SEEX * 2 + 8); WINDOW* w_item_info = newwin(12, 48, 12, SEEX * 2 + 8); int maxitems = 9; // Number of items to show at one time. - std::vector here = m.i_at(posx, posy); + std::vector here = from_veh? veh.parts[veh_part].items : m.i_at(posx, posy); bool getitem[here.size()]; for (int i = 0; i < here.size(); i++) getitem[i] = false; @@ -4130,34 +4656,46 @@ void game::pickup(int posx, int posy, int min) decrease_nextinv(); } else if (u.volume_carried() + here[i].volume() > u.volume_capacity()) { if (u.is_armed()) { - if (u.weapon.type->id < num_items && // Not a bionic + if (!u.weapon.has_flag(IF_NO_UNWIELD) && // Not a bionic query_yn("Drop your %s and pick up %s?", u.weapon.tname(this).c_str(), here[i].tname(this).c_str())) { m.add_item(posx, posy, u.remove_weapon()); u.i_add(here[i]); u.wield(this, u.inv.size() - 1); u.moves -= 100; - m.i_rem(posx, posy, curmit); + if (from_veh) + veh.remove_item (veh_part, curmit); + else + m.i_rem(posx, posy, curmit); curmit--; } else decrease_nextinv(); } else { u.i_add(here[i]); u.wield(this, u.inv.size() - 1); - m.i_rem(posx, posy, curmit); + if (from_veh) + veh.remove_item (veh_part, curmit); + else + m.i_rem(posx, posy, curmit); curmit--; u.moves -= 100; } } else if (!u.is_armed() && (u.volume_carried() + here[i].volume() > u.volume_capacity() - 2 || - here[i].is_weap())) { + here[i].is_weap() || here[i].is_gun())) { u.weapon = here[i]; - m.i_rem(posx, posy, curmit); + if (from_veh) + veh.remove_item (veh_part, curmit); + else + m.i_rem(posx, posy, curmit); u.moves -= 100; curmit--; } else { u.i_add(here[i]); - m.i_rem(posx, posy, curmit); + if (from_veh) + veh.remove_item (veh_part, curmit); + else + m.i_rem(posx, posy, curmit); u.moves -= 100; curmit--; } @@ -4188,7 +4726,31 @@ bool game::handle_liquid(item &liquid, bool from_ground, bool infinite) query_yn("Pour %s on the ground?", liquid.tname(this).c_str())) { m.add_item(u.posx, u.posy, liquid); return true; - } else { + } else if (liquid.type->id == itm_gasoline && vehicle_near() && + query_yn ("Refill vehicle?")) { + int vx = u.posx, vy = u.posy; + if (pl_choose_vehicle(vx, vy)) { + vehicle &veh = m.veh_at (vx, vy); + if (veh.type != veh_null) { + int ftype = AT_GAS; + int fuel_cap = veh.fuel_capacity(ftype); + int fuel_amnt = veh.fuel_left(ftype); + if (fuel_cap < 1) + add_msg ("This vehicle doesn't use %s.", veh.fuel_name(ftype).c_str()); + else if (fuel_amnt == fuel_cap) + add_msg ("Already full."); + else { + veh.refill (AT_GAS, liquid.charges); + add_msg ("You refill %s with %s%s.", veh.name.c_str(), + veh.fuel_name(ftype).c_str(), + veh.fuel_left(ftype) >= fuel_cap? " to its maximum" : ""); + return true; + } + } else // if (veh.type != veh_null) + add_msg ("There's no any vehicle there."); + return false; + } // if (pl_choose_vehicle(vx, vy)) + } else { // Not filling vehicle std::stringstream text; text << "Container for " << liquid.tname(this); char ch = inv(text.str().c_str()); @@ -4297,14 +4859,46 @@ void game::drop() if (dropped[i].type->id != first) same = false; } + + int veh_part = 0; + vehicle &veh = m.veh_at(u.posx, u.posy, veh_part); + veh_part = veh.part_with_feature (veh_part, vpf_cargo); + bool to_veh = veh.type != veh_null && veh_part >= 0; if (dropped.size() == 1 || same) - add_msg("You drop your %s%s.", dropped[0].tname(this).c_str(), + { + if (to_veh) + add_msg("You put your %s%s in %s's %s.", dropped[0].tname(this).c_str(), + (dropped.size() == 1 ? "" : "s"), veh.name.c_str(), veh.part_info(veh_part).name); + else + add_msg("You drop your %s%s.", dropped[0].tname(this).c_str(), (dropped.size() == 1 ? "" : "s")); + } else - add_msg("You drop several items."); - - for (int i = 0; i< dropped.size(); i++) - m.add_item(u.posx, u.posy, dropped[i]); + { + if (to_veh) + add_msg("You put several items in %s's %s.", veh.name.c_str(), veh.part_info(veh_part).name); + else + add_msg("You drop several items."); + } + + bool vh_overflow = false; + int i = 0; + if (to_veh) + { + for (; i < dropped.size(); i++) + if (!veh.add_item (veh_part, dropped[i])) + { + vh_overflow = true; + break; + } + if (vh_overflow) + add_msg ("Trunk is full, so some items fall on the ground."); + } + if (!to_veh || vh_overflow) + for (; i< dropped.size(); i++) + { + m.add_item(u.posx, u.posy, dropped[i]); + } } void game::drop_in_direction() @@ -4319,6 +4913,11 @@ void game::drop_in_direction() } dirx += u.posx; diry += u.posy; + int veh_part = 0; + vehicle &veh = m.veh_at(dirx, diry, veh_part); + veh_part = veh.part_with_feature (veh_part, vpf_cargo); + bool to_veh = veh.type != veh_null && veh_part >= 0; + if (m.has_flag(noitem, dirx, diry) || m.has_flag(sealed, dirx, diry)) { add_msg("You can't place items there!"); return; @@ -4341,14 +4940,33 @@ void game::drop_in_direction() same = false; } if (dropped.size() == 1 || same) - add_msg("You %s your %s%s %s the %s.", verb.c_str(), + { + if (to_veh) + add_msg("You put your %s%s in %s's %s.", dropped[0].tname(this).c_str(), + (dropped.size() == 1 ? "" : "s"), veh.name.c_str(), veh.part_info(veh_part).name); + else + add_msg("You %s your %s%s %s the %s.", verb.c_str(), dropped[0].tname(this).c_str(), (dropped.size() == 1 ? "" : "s"), prep.c_str(), m.tername(dirx, diry).c_str()); + } else - add_msg("You %s several items %s the %s.", verb.c_str(), prep.c_str(), + { + if (to_veh) + add_msg("You put several items in %s's %s.", veh.name.c_str(), veh.part_info(veh_part).name); + else + add_msg("You %s several items %s the %s.", verb.c_str(), prep.c_str(), m.tername(dirx, diry).c_str()); - + } + if (to_veh) + { + bool vh_overflow = false; + for (int i = 0; i < dropped.size(); i++) + vh_overflow = vh_overflow || !veh.add_item (veh_part, dropped[i]); + if (vh_overflow) + add_msg ("Trunk is full, so some items fall on the ground."); + } + else for (int i = 0; i< dropped.size(); i++) m.add_item(dirx, diry, dropped[i]); } @@ -4452,6 +5070,11 @@ void game::plfire(bool burst) { if (!u.weapon.is_gun()) return; + vehicle &veh = m.veh_at (u.posx, u.posy); + if (veh.type != veh_null && veh.player_in_control(&u) && u.weapon.is_two_handed(&u)) { + add_msg ("You need free arm to drive!"); + return; + } if (u.weapon.has_flag(IF_RELOAD_AND_SHOOT)) { int index = u.weapon.pick_reload_ammo(u, true); if (index == -1) { @@ -4579,7 +5202,7 @@ void game::butcher() time_to_cut += factor * 5; // Penalty for poor tool if (time_to_cut < 250) time_to_cut = 250; - u.activity = player_activity(ACT_BUTCHER, time_to_cut, corpses[i]); + u.assign_activity(ACT_BUTCHER, time_to_cut, corpses[i]); u.moves = 0; return; } @@ -4680,8 +5303,7 @@ void game::eat() add_msg("You don't have item '%c'!", ch); return; } - if (!u.eat(this, u.lookup_item(ch))) - add_msg("You can't eat that!"); + u.eat(this, u.lookup_item(ch)); } void game::wear() @@ -4719,7 +5341,7 @@ single action.", u.weapon.tname().c_str()); add_msg("Out of ammo!"); return; } - u.activity = player_activity(ACT_RELOAD, u.weapon.reload_time(u), index); + u.assign_activity(ACT_RELOAD, u.weapon.reload_time(u), index); u.moves = 0; } else if (u.weapon.is_tool()) { it_tool* tool = dynamic_cast(u.weapon.type); @@ -4733,7 +5355,7 @@ single action.", u.weapon.tname().c_str()); add_msg("Out of %s!", ammo_name(tool->ammo).c_str()); return; } - u.activity = player_activity(ACT_RELOAD, u.weapon.reload_time(u), index); + u.assign_activity(ACT_RELOAD, u.weapon.reload_time(u), index); u.moves = 0; } else if (!u.is_armed()) add_msg("You're not wielding anything."); @@ -4841,12 +5463,16 @@ void game::unload() void game::wield() { - if (u.weapon.type->id > num_items && u.weapon.type->id < num_all_items) { + if (u.weapon.has_flag(IF_NO_UNWIELD)) { // Bionics can't be unwielded add_msg("You cannot unwield your %s.", u.weapon.tname(this).c_str()); return; } - char ch = inv("Wield item:"); + char ch; + if (u.styles.empty()) + ch = inv("Wield item:"); + else + ch = inv("Wield item: Press - to choose a style"); bool success = false; if (ch == '-') success = u.wield(this, -3); @@ -4873,10 +5499,13 @@ void game::chat() int junk; for (int i = 0; i < active_npc.size(); i++) { if (u_see(active_npc[i].posx, active_npc[i].posy, junk) && - trig_dist(u.posx, u.posy, active_npc[i].posx, active_npc[i].posy) <= 12) + rl_dist(u.posx, u.posy, active_npc[i].posx, active_npc[i].posy) <= 24) available.push_back(&active_npc[i]); } - if (available.size() == 1) + if (available.size() == 0) { + add_msg("There's no-one close enough to talk to."); + return; + } else if (available.size() == 1) available[0]->talk_to_u(this); else { WINDOW *w = newwin(available.size() + 3, 40, 10, 20); @@ -4900,6 +5529,46 @@ void game::chat() u.moves -= 100; } +void game::pldrive(int x, int y) +{ + if (run_mode == 2) { // Monsters around and we don't wanna run + add_msg("Monster spotted--run mode is on! " + "(Press '!' to turn it off or ' to ignore monster.)"); + return; + } + int part = -1; + vehicle &veh = m.veh_at (u.posx, u.posy, part); + if (veh.type == veh_null) { + debugmsg ("game::pldrive error: can't find vehicle! Drive mode is now off."); + u.in_vehicle = false; + return; + } + int pctr = veh.part_with_feature (part, vpf_controls); + if (pctr < 0) { + add_msg ("You can't drive vehicle from here. You need controls!"); + return; + } + + int thr_amount = 10 * 100; + if (veh.cruise_on) + veh.cruise_thrust (-y * thr_amount); + else { + veh.thrust (-y); + } + veh.turn (15 * x); + if (veh.skidding && veh.valid_wheel_config()) { + if (rng (0, 40) < u.dex_cur + u.sklevel[sk_driving] * 2) { + add_msg ("You regain control of %s.", veh.name.c_str()); + veh.skidding = false; + veh.move.init (veh.turn_dir); + } + } + + u.moves = 0; + if (x != 0 && veh.velocity != 0 && one_in(4)) + u.practice(sk_driving, 1); +} + void game::plmove(int x, int y) { if (run_mode == 2) { // Monsters around and we don't wanna run @@ -4907,8 +5576,13 @@ void game::plmove(int x, int y) (Press '!' to turn it off or ' to ignore monster.)"); return; } - x += u.posx; - y += u.posy; + if (u.has_disease(DI_STUNNED)) { + x = rng(u.posx - 1, u.posx + 1); + y = rng(u.posy - 1, u.posy + 1); + } else { + x += u.posx; + y += u.posy; + } // Check if our movement is actually an attack on a monster int mondex = mon_at(x, y); bool displace = false; // Are we displacing a monster? @@ -4929,72 +5603,12 @@ void game::plmove(int x, int y) return; // Cancel the attack body_part bphit; int hitdam = 0, hitcut = 0; - bool success = u.hit_player(this, active_npc[npcdex], bphit, hitdam, hitcut); - if (u.recoil <= 30) - u.recoil += 6; - u.moves -= (80 + u.weapon.volume() * 2 + u.weapon.weight() + - u.encumb(bp_torso)); - if (!success) { - int stumble_pen = u.weapon.volume() + int(u.weapon.weight() / 2); - if (u.has_trait(PF_DEFT)) - stumble_pen = int(stumble_pen * .4) - 10; - if (stumble_pen < 0) - stumble_pen = 0; - if (stumble_pen > 0 && (one_in(16 - u.str_cur) || one_in(22 - u.dex_cur))) - stumble_pen = rng(0, stumble_pen); - if (stumble_pen >= 30) - add_msg("You miss and stumble with the momentum."); - else if (stumble_pen >= 10) - add_msg("You swing wildly and miss."); - else - add_msg("You miss."); - u.moves -= stumble_pen; - } else { // We hit! - int side = rng(0, 1); - add_msg("You hit %s's %s.", active_npc[npcdex].name.c_str(), - body_part_name(bphit, side).c_str()); - if (u.has_bionic(bio_shock) && u.power_level >= 2 && one_in(3) && - (!u.is_armed() || u.weapon.type->id > num_items)) { - add_msg("You shock %s!", active_npc[npcdex].name.c_str()); - int shock = rng(2, 5); - hitdam += shock; - active_npc[npcdex].moves -= shock * 300; - u.power_level -= 2; - } - if (u.has_bionic(bio_heat_absorb) && u.power_level >= 1 && !u.is_armed()) { - u.power_level--; - if (one_in(2)) { - add_msg("You drain %s's body heat!", active_npc[npcdex].name.c_str()); - u.charge_power(rng(3, 5)); - hitdam += rng(2, 6); - } else { - add_msg("You attempt to drain body heat, but fail."); - } - } - if (u.has_trait(PF_FANGS) && one_in(20-u.dex_cur)) { - add_msg("You sink your fangs into %s!", active_npc[npcdex].name.c_str()); - hitcut += 18; - } - sound(u.posx, u.posy, 6, ""); - if (u.weapon.made_of(GLASS) && - rng(0, u.weapon.volume() + 8) < u.weapon.volume() + u.str_cur) { -// Glass weapon shattered - add_msg("Your %s shatters!", u.weapon.tname(this).c_str()); - for (int i = 0; i < u.weapon.contents.size(); i++) - m.add_item(x, y, u.weapon.contents[i]); - sound(u.posx, u.posy, 16, ""); - u.hit(this, bp_hands, 1, 0, rng(0, u.weapon.volume() * 2)); - if (u.weapon.is_two_handed(&u))// Hurt left arm too, if it was big - u.hit(this, bp_hands, 0, 0, rng(0, u.weapon.volume())); - hitcut += rng(0, int(u.weapon.volume() * 1.5)); // Hurt the monster - u.remove_weapon(); - } - active_npc[npcdex].hit(this, bphit, side, hitdam, hitcut); - if (active_npc[npcdex].hp_cur[hp_head] <= 0 || - active_npc[npcdex].hp_cur[hp_torso] <= 0 ) { - active_npc[npcdex].die(this, true); - active_npc.erase(active_npc.begin() + npcdex); - } + u.hit_player(this, active_npc[npcdex]); + active_npc[npcdex].make_angry(); + if (active_npc[npcdex].hp_cur[hp_head] <= 0 || + active_npc[npcdex].hp_cur[hp_torso] <= 0 ) { + active_npc[npcdex].die(this, true); + active_npc.erase(active_npc.begin() + npcdex); } return; } @@ -5030,10 +5644,42 @@ void game::plmove(int x, int y) u.rem_disease(DI_IN_PIT); } } + if (u.has_disease(DI_DOWNED)) { + if (rng(0, 40) > u.dex_cur + int(u.str_cur / 2)) { + add_msg("You struggle to stand."); + u.moves -= 100; + return; + } else { + add_msg("You stand up."); + u.rem_disease(DI_DOWNED); + u.moves -= 100; + return; + } + } + + int vpart = -1, dpart = -1; + vehicle &veh = m.veh_at(x, y, vpart); + bool veh_closed_door = false; + if (veh.type != veh_null) + { + dpart = veh.part_with_feature (vpart, vpf_openable); + veh_closed_door = dpart >= 0 && !veh.parts[dpart].open; + } + if (m.move_cost(x, y) > 0) { // move_cost() of 0 = impassible (e.g. a wall) if (u.underwater) u.underwater = false; - int movecost; + dpart = veh.type != veh_null? veh.part_with_feature (vpart, vpf_seat) : -1; + bool can_board = dpart >= 0 && !veh.parts[dpart].passenger; +/* if (veh.type != veh_null) + add_msg ("vp=%d dp=%d can=%c", vpart, dpart, can_board? 'y' : 'n',);*/ + if (can_board && query_yn("Board vehicle?")) + { // empty vehicle's seat ahead + m.board_vehicle (this, x, y, &u); + u.moves -= 200; + return; + } + if (m.field_at(x, y).is_dangerous() && !query_yn("Really step into that %s?", m.field_at(x, y).name().c_str())) return; @@ -5056,7 +5702,12 @@ void game::plmove(int x, int y) } if ((!u.has_trait(PF_PARKOUR) && m.move_cost(x, y) > 2) || ( u.has_trait(PF_PARKOUR) && m.move_cost(x, y) > 4 )) - add_msg("Moving past this %s is slow!", m.tername(x, y).c_str()); + { + if (veh.type != veh_null) + add_msg("Moving past this %s is slow!", veh.part_info(vpart).name); + else + add_msg("Moving past this %s is slow!", m.tername(x, y).c_str()); + } if (m.has_flag(rough, x, y)) { if (one_in(5) && u.armor_bash(bp_feet) < rng(1, 5)) { add_msg("You hurt your feet on the %s!", m.tername(x, y).c_str()); @@ -5081,10 +5732,12 @@ void game::plmove(int x, int y) add_msg("The water puts out the flames!"); u.rem_disease(DI_ONFIRE); } - if (displace) { // We displaced a friendly monster! +// displace is set at the top of this function. + if (displace) { // We displaced a friendly monster! // Immobile monsters can't be displaced. if (z[mondex].has_flag(MF_IMMOBILE)) { // ...except that turrets can be picked up. +// TODO: Make there a flag, instead of hard-coded to mon_turret if (z[mondex].type->id == mon_turret) { if (query_yn("Deactivate the turret?")) { z.erase(z.begin() + mondex); @@ -5113,6 +5766,49 @@ void game::plmove(int x, int y) } } +// Some martial art styles have special effects that trigger when we move + switch (u.weapon.type->id) { + + case itm_style_capoeira: + if (u.disease_level(DI_ATTACK_BOOST) < 2) + u.add_disease(DI_ATTACK_BOOST, 2, this, 2, 2); + if (u.disease_level(DI_DODGE_BOOST) < 2) + u.add_disease(DI_DODGE_BOOST, 2, this, 2, 2); + break; + + case itm_style_ninjutsu: + u.add_disease(DI_ATTACK_BOOST, 2, this, 1, 3); + break; + + case itm_style_crane: + if (!u.has_disease(DI_DODGE_BOOST)) + u.add_disease(DI_DODGE_BOOST, 1, this, 3, 3); + break; + + case itm_style_leopard: + u.add_disease(DI_ATTACK_BOOST, 2, this, 1, 4); + break; + + case itm_style_dragon: + if (!u.has_disease(DI_DAMAGE_BOOST)) + u.add_disease(DI_DAMAGE_BOOST, 2, this, 3, 3); + break; + + case itm_style_lizard: { + bool wall = false; + for (int wallx = x - 1; wallx <= x + 1 && !wall; wallx++) { + for (int wally = y - 1; wally <= y + 1 && !wall; wally++) { + if (m.has_flag(supports_roof, wallx, wally)) + wall = true; + } + } + if (wall) + u.add_disease(DI_ATTACK_BOOST, 2, this, 2, 8); + else + u.rem_disease(DI_ATTACK_BOOST); + } break; + } + // List items here if (!u.has_disease(DI_BLIND) && m.i_at(x, y).size() <= 3 && m.i_at(x, y).size() != 0) { @@ -5128,6 +5824,13 @@ void game::plmove(int x, int y) add_msg(buff.c_str()); } else if (m.i_at(x, y).size() != 0) add_msg("There are many items here."); + + } else if (veh_closed_door) { // move_cost <= 0 + veh.parts[dpart].open = 1; + veh.insides_dirty = true; + u.moves -= 100; + add_msg ("You open %s's %s.", veh.name.c_str(), veh.part_info(dpart).name); + } else if (m.has_flag(swimmable, x, y)) { // Dive into water! // Requires confirmation if we were on dry land previously if ((m.has_flag(swimmable, u.posx, u.posy) && @@ -5135,14 +5838,12 @@ void game::plmove(int x, int y) if (m.move_cost(u.posx, u.posy) > 0 && u.swim_speed() < 500) add_msg("You start swimming. Press '>' to dive underwater."); plswim(x, y); - if (u.has_disease(DI_ONFIRE)) { - add_msg("The water puts out the flames!"); - u.rem_disease(DI_ONFIRE); - } } + } else { // Invalid move - if (u.has_disease(DI_BLIND)) { - add_msg("You bump into something!"); + if (u.has_disease(DI_BLIND) || u.has_disease(DI_STUNNED)) { +// Only lose movement if we're blind + add_msg("You bump into a %s!", m.tername(x, y).c_str()); u.moves -= 100; } else if (m.open_door(x, y, m.ter(u.posx, u.posy) == t_floor)) u.moves -= 100; @@ -5164,6 +5865,10 @@ void game::plswim(int x, int y) debugmsg("Tried to swim in %s!", m.tername(x, y).c_str()); return; } + if (u.has_disease(DI_ONFIRE)) { + add_msg("The water puts out the flames!"); + u.rem_disease(DI_ONFIRE); + } int movecost = u.swim_speed(); u.practice(sk_swimming, 1); if (movecost >= 500) { @@ -5186,6 +5891,133 @@ void game::plswim(int x, int y) } } +void game::fling_player_or_monster(player *p, monster *zz, int dir, int flvel) +{ + int steps = 0; + bool is_u = p && (p == &u); + int dam1, dam2; + + bool is_player; + if (p) + is_player = true; + else + if (zz) + is_player = false; + else + { + debugmsg ("game::fling neither player nor monster"); + return; + } + + tileray tdir(dir); + std::string sname, snd; + if (p) + { + if (is_u) + sname = std::string ("You are"); + else + sname = p->name + " is"; + } + else + sname = zz->name() + " is"; + int range = flvel / 10; + int vel1 = flvel; + int x = (is_player? p->posx : zz->posx); + int y = (is_player? p->posy : zz->posy); + while (range > 0) + { + tdir.advance(); + x = (is_player? p->posx : zz->posx) + tdir.dx(); + y = (is_player? p->posy : zz->posy) + tdir.dy(); + std::string dname; + bool thru = true; + bool slam = false; + int mondex = mon_at(x, y); + dam1 = flvel / 3 + rng (0, flvel * 1 / 3); + if (mondex >= 0) + { + slam = true; + dname = z[mondex].name(); + dam2 = flvel / 3 + rng (0, flvel * 1 / 3); + if (z[mondex].hurt(dam2)) + kill_mon(mondex); + else + thru = false; + if (is_player) + p->hitall (this, dam1, 40); + else + zz->hurt(dam1); + } + else + if (m.move_cost(x, y) == 0 && !m.has_flag(swimmable, x, y)) // not passable + { + slam = true; + int vpart; + vehicle &veh = m.veh_at(x, y, vpart); + dname = veh.type != veh_null? veh.part_info(vpart).name : m.tername(x, y).c_str(); + if (m.has_flag(bashable, x, y)) + thru = m.bash(x, y, flvel, snd); + else + thru = false; + if (snd.length() > 0) + add_msg ("You hear a %s", snd.c_str()); + if (is_player) + p->hitall (this, dam1, 40); + else + zz->hurt (dam1); + flvel = flvel / 2; + } + if (slam) + add_msg ("%s slammed against the %s for %d damage!", sname.c_str(), dname.c_str(), dam1); + if (thru) + { + if (is_player) + { + p->posx = x; + p->posy = y; + } + else + { + zz->posx = x; + zz->posy = y; + } + } + else + break; + range--; + steps++; + timespec ts; // Timespec for the animation + ts.tv_sec = 0; + ts.tv_nsec = 50000000; + nanosleep (&ts, 0); + } + + if (!m.has_flag(swimmable, x, y)) + { + // fall on ground + dam1 = rng (flvel / 3, flvel * 2 / 3) / 2; + if (is_player) + { + int dex_reduce = p->dex_cur < 4? 4 : p->dex_cur; + dam1 = dam1 * 8 / dex_reduce; + if (p->has_trait(PF_PARKOUR)) + dam1 /= 2; + if (dam1 > 0) + p->hitall (this, dam1, 40); + } + else + zz->hurt (dam1); + if (is_u) + if (dam1 > 0) + add_msg ("You fall on the ground for %d damage.", dam1); + else + add_msg ("You fall on the ground."); + } + else + if (is_u) + add_msg ("You fall into water."); +} + void game::vertical_move(int movez, bool force) { // > and < are used for diving underwater. @@ -5265,7 +6097,7 @@ void game::vertical_move(int movez, bool force) bool replace_monsters = false; // Replace the stair monsters if we just came back if (abs(monstairx - levx) <= 1 && abs(monstairy - levy) <= 1 && - monstairz == levz) + monstairz == levz + movez) replace_monsters = true; if (!force) { @@ -5417,7 +6249,7 @@ void game::update_map(int &x, int &y) } set_adjacent_overmaps(); -// Shift monsters + // Shift monsters for (int i = 0; i < z.size(); i++) { z[i].shift(shiftx, shifty); if (z[i].posx < 0 - SEEX || z[i].posy < 0 - SEEX || @@ -5689,18 +6521,23 @@ void game::spawn_mon(int shiftx, int shifty) int iter; int t; // Create a new NPC? -/* - if (one_in(50 + 5 * cur_om.npcs.size())) { - npc temp; - temp.randomize(this); - temp.normalize(this); - temp.spawn_at(&cur_om, levx + (1 * rng(-2, 2)), levy + (1 * rng(-2, 2))); - temp.posx = SEEX * 2 * (temp.mapx - levx) + rng(0 - SEEX, SEEX); - temp.posy = SEEY * 2 * (temp.mapy - levy) + rng(0 - SEEY, SEEY); - temp.attitude = NPCATT_TALK; - active_npc.push_back(temp); - } -*/ + if (one_in(50 + 5 * cur_om.npcs.size())) { + npc tmp; + tmp.normalize(this); + tmp.randomize(this); + //tmp.stock_missions(this); + tmp.spawn_at(&cur_om, levx + (1 * rng(-5, 5)), levy + (1 * rng(-5, 5))); + tmp.posx = SEEX * 2 * (tmp.mapx - levx) + rng(0 - SEEX, SEEX); + tmp.posy = SEEY * 2 * (tmp.mapy - levy) + rng(0 - SEEY, SEEY); + tmp.form_opinion(&u); + tmp.attitude = NPCATT_TALK; + tmp.mission = NPC_MISSION_NULL; + int mission_index = reserve_random_mission(ORIGIN_ANY_NPC, + om_location(), tmp.id); + if (mission_index != -1) + tmp.chatbin.missions.push_back(mission_index); + active_npc.push_back(tmp); + } // Now, spawn monsters (perhaps) monster zom; @@ -5717,7 +6554,7 @@ void game::spawn_mon(int shiftx, int shifty) group++; cur_om.zg[i].population -= group; if (group > 0) // If we spawned some zombies, advance the timer - nextspawn += rng(group + z.size() * 3, group * 3 + z.size() * 7); + nextspawn += rng(group * 3 + z.size() * 5, group * 10 + z.size() * 10); for (int j = 0; j < group; j++) { // For each monster in the group... mon_id type = valid_monster_from(moncats[cur_om.zg[i].type]); if (type == mon_null) @@ -5840,7 +6677,7 @@ void game::wait() case 5: time = 180000; break; case 6: time = 360000; break; } - u.activity = player_activity(ACT_WAIT, time, 0); + u.assign_activity(ACT_WAIT, time, 0); u.moves = 0; } diff --git a/game.h b/game.h index 5c938bca63..5952c9fcfb 100644 --- a/game.h +++ b/game.h @@ -30,6 +30,8 @@ #define BULLET_SPEED 10000000 #define EXPLOSION_SPEED 70000000 +#define PICKUP_RANGE 2 + enum tut_type { TUT_NULL, TUT_BASIC, TUT_COMBAT, @@ -88,9 +90,11 @@ class game // Move the player vertically, if (force) then they fell void vertical_move(int z, bool force); void use_computer(int x, int y); + bool pl_refill_vehicle (vehicle &veh, int part, bool test=false); void resonance_cascade(int x, int y); void emp_blast(int x, int y); int npc_at(int x, int y); // Index of the npc at (x, y); -1 for none + // void build_monmap(); // Caches data for mon_at() int mon_at(int x, int y); // Index of the monster at (x, y); -1 for none bool is_empty(int x, int y); // True if no PC, no monster, move cost > 0 bool isBetween(int test, int down, int up); @@ -108,8 +112,8 @@ class game void cancel_activity(); void cancel_activity_query(const char* message, ...); int assign_mission_id(); // Just returns the next available one - void give_mission(mission_id type); - void assign_mission(int id); + void give_mission(mission_id type); // Create the mission and assign it + void assign_mission(int id); // Just assign an existing mission // reserve_mission() creates a new mission of the given type and pushes it to // active_missions. The function returns the UID of the new mission, which can // then be passed to a MacGuffin or something else that needs to track a mission @@ -128,6 +132,9 @@ class game void teleport(player *p = NULL); void plswim(int x, int y); // Called by plmove. Handles swimming + // when player is thrown (by impact or something) + void fling_player_or_monster(player *p, monster *zz, int dir, int flvel); + void nuke(int x, int y); std::vector factions_at(int x, int y); int& scent(int x, int y); @@ -160,6 +167,7 @@ class game std::vector itypes; std::vector mtypes; + std::vector vtypes; std::vector traps; std::vector recipes; // The list of valid recipes std::vector constructions; // The list of constructions @@ -189,7 +197,7 @@ class game ter_id dragging; std::vector items_dragged; int weight_dragged; // Computed once, when you start dragging - bool debugmon; + bool debugmon; // Display data... TODO: Make this more portable? WINDOW *w_terrain; WINDOW *w_minimap; @@ -217,6 +225,7 @@ class game void init_construction(); // Initializes construction "recipes" void init_missions(); // Initializes mission templates void init_mutations(); // Initializes mutation "tech tree" + void init_vehicles(); // Initializes vehicle types void load_keyboard_settings(); // Load keybindings from disk @@ -228,6 +237,7 @@ class game void monster_wish(); // Create a monster void mutation_wish(); // Mutate + void pldrive(int x, int y); // drive vehicle void plmove(int x, int y); // Standard movement; handles attacks, traps, &c void wait(); // Long wait (player action) '^' void open(); // Open a door 'o' @@ -243,7 +253,12 @@ class game int level = -1, bool cont = false); void place_construction(constructable *con); // See construction.cpp void complete_construction(); // See construction.cpp + bool pl_choose_vehicle (int &x, int &y); + bool vehicle_near (); + void handbrake (); void examine();// Examine nearby terrain 'e' + // open vehicle interaction screen + void exam_vehicle(vehicle &veh, int examx, int examy, int cx=0, int cy=0); void pickup(int posx, int posy, int min);// Pickup items; ',' or via examine() // Pick where to put liquid; false if it's left where it was bool handle_liquid(item &liquid, bool from_ground, bool infinite); @@ -335,6 +350,7 @@ class game std::vector messages; // Messages to be printed unsigned char curmes; // The last-seen message. Older than 256 is deleted. int grscent[SEEX * MAPSIZE][SEEY * MAPSIZE]; // The scent map + //int monmap[SEEX * MAPSIZE][SEEY * MAPSIZE]; // Temp monster map, for mon_at() int nulscent; // Returned for OOB scent checks std::vector events; // Game events to be processed int kills[num_monsters]; // Player's kill count diff --git a/help.cpp b/help.cpp index ff53416d98..48b2e819ca 100644 --- a/help.cpp +++ b/help.cpp @@ -37,7 +37,8 @@ i: Crafting\n\ j: Traps\n\ k: Items overview\n\ l: Combat\n\ -m: Survival tips\n\ +m: Unarmed Styles\n\ +n: Survival tips\n\ \n\ 1: List of all commands\n\ 2: List of item types and data\n\ @@ -361,6 +362,30 @@ escape tactic."); case 'M': erase(); mvprintz(0, 0, c_white, "\ +For the unarmed fighter, a variety of fighting styles are available. You can\n\ +start with your choice of a single, commonly-taught style by starting with\n\ +the Martial Arts Training trait. Many, many more can be taught by NPCs.\n\ +\n\ +To select a fighting style, press _ (underscore). If you are already unarmed\n\ +this will make you start using the style. Otherwise, it will be locked in as\n\ +your default unarmed style; to start using it, press w-.\n\ +\n\ +Most styles have a variety of special moves associated with them. Most have\n\ +a skill requirement, and will be unavailable until you reach a level of\n\ +unarmed skill. You can check the moves by examining your style via the\n\ +inventory screen (i key).\n\ +\n\ +Many styles also have special effects unlocked under certain conditions.\n\ +These are varied and unique to each style, and range from special combo moves\n\ +to bonuses depending on the situation you are in. You can check these by\n\ +examining your style."); + getch(); + break; + + case 'n': + case 'N': + erase(); + mvprintz(0, 0, c_white, "\ The first thing to do is to check your home for useful items. Your initial\n\ storage is limited, and a backpack, trenchcoat, or other storage medium will\n\ let you carry a lot more. Finding a weapon is important; frying pans, butcher\n\ @@ -401,14 +426,14 @@ e - Examine terrain, open container < or > - Go up or down stairs\n\ mvprintz(7, 0, c_white, "ITEMS:"); mvprintz(8, 0, c_ltgray, "\ i - View Inventory d,D - Drop item (with direction)\n\ -w - Wield item t - Throw item\n\ +w - Wield item (w- wields nothing) t - Throw item\n\ W - Wear item T - Take off item\n\ a - Activate tool E - Eat comestible\n\ r - Reload wielded gun or tool U - Unload wielded gun or tool\n\ -f - Fire gun F - Burst-fire gun\n\ +f - Fire gun (F burst-fires) B - Butcher a corpse\n\ p - Power up / List bionics R - Read book\n\ -& - Craft items B - Butcher a corpse\n\ -= - Reassign inventory letter * - Construct"); +& - Craft items * - Construct\n\ += - Reassign inventory letter _ - Select Melee style"); mvprintz(17, 0, c_white, "INFORMATION:"); mvprintz(18, 0, c_ltgray, "\ @ - View character status : or m - Open world map\n\ diff --git a/inventory.cpp b/inventory.cpp index ed3f6f90dd..c943d34d9d 100644 --- a/inventory.cpp +++ b/inventory.cpp @@ -49,6 +49,15 @@ int inventory::size() const return items.size(); } +int inventory::num_items() const +{ + int ret = 0; + for (int i = 0; i < items.size(); i++) + ret += items[i].size(); + + return ret; +} + inventory& inventory::operator= (inventory &rhs) { if (this == &rhs) @@ -130,6 +139,8 @@ void inventory::push_back(std::vector newits) void inventory::add_item(item newit) { + if (newit.is_style()) + return; // Styles never belong in our inventory. for (int i = 0; i < items.size(); i++) { if (items[i][0].stacks_with(newit)) { newit.invlet = items[i][0].invlet; diff --git a/inventory.h b/inventory.h index c9d36f13f2..15d6dbefd0 100644 --- a/inventory.h +++ b/inventory.h @@ -18,6 +18,7 @@ class inventory item& front(); item& back(); int size() const; + int num_items() const; inventory& operator= (inventory &rhs); inventory& operator= (const inventory &rhs); diff --git a/inventory_ui.cpp b/inventory_ui.cpp index dc47176bf9..2c6518880d 100644 --- a/inventory_ui.cpp +++ b/inventory_ui.cpp @@ -43,8 +43,8 @@ void print_inv_statics(game *g, WINDOW* w_inv, std::string title, mvwprintz(w_inv, 3, 40, c_white, "%c + %s", g->u.weapon.invlet, g->u.weapname().c_str()); else - mvwprintz(w_inv, 3, 40, c_ltgray, "%c - %s", g->u.weapon.invlet, - g->u.weapname().c_str()); + mvwprintz(w_inv, 3, 40, g->u.weapon.color_in_inventory(&(g->u)), "%c - %s", + g->u.weapon.invlet, g->u.weapname().c_str()); } else mvwprintz(w_inv, 3, 42, c_ltgray, g->u.weapname().c_str()); // Print worn items @@ -110,7 +110,7 @@ char game::inv(std::string title) std::vector firsts = find_firsts(u.inv); do { - if (ch == '<' && start > 0) { + if (ch == '<' && start > 0) { // Clear lines and shift for (int i = 1; i < 25; i++) mvwprintz(w_inv, i, 0, c_black, " "); start -= maxitems; @@ -118,7 +118,7 @@ char game::inv(std::string title) start = 0; mvwprintw(w_inv, maxitems + 2, 0, " "); } - if (ch == '>' && cur_it < u.inv.size()) { + if (ch == '>' && cur_it < u.inv.size()) { // Clear lines and shift start = cur_it; mvwprintw(w_inv, maxitems + 2, 12, " "); for (int i = 1; i < 25; i++) @@ -137,7 +137,8 @@ char game::inv(std::string title) } if (cur_it < u.inv.size()) { mvwputch (w_inv, cur_line, 0, c_white, u.inv[cur_it].invlet); - mvwprintw(w_inv, cur_line, 1, " %s", u.inv[cur_it].tname(this).c_str()); + mvwprintz(w_inv, cur_line, 1, u.inv[cur_it].color_in_inventory(&u), " %s", + u.inv[cur_it].tname(this).c_str()); if (u.inv.stack_at(cur_it).size() > 1) wprintw(w_inv, " [%d]", u.inv.stack_at(cur_it).size()); if (u.inv[cur_it].charges > 0) @@ -164,6 +165,8 @@ char game::inv(std::string title) std::vector game::multidrop() { + u.inv.restack(this); + u.sort_inv(); WINDOW* w_inv = newwin(25, 80, 0, 0); const int maxitems = 20; // Number of items to show at one time. int dropping[u.inv.size()]; // Count of how many we'll drop from each stack @@ -172,8 +175,6 @@ std::vector game::multidrop() int count = 0; // The current count std::vector weapon_and_armor; // Always single, not counted bool warned_about_bionic = false; // Printed add_msg re: dropping bionics - u.inv.restack(this); - u.sort_inv(); print_inv_statics(this, w_inv, "Multidrop:", weapon_and_armor); // Gun, ammo, weapon, armor, food, tool, book, other std::vector firsts = find_firsts(u.inv); diff --git a/item.cpp b/item.cpp index a85af72675..fa12c5d95c 100644 --- a/item.cpp +++ b/item.cpp @@ -13,6 +13,7 @@ bool is_flammable(material m); +std::string default_technique_name(technique_id tech); item::item() { @@ -202,7 +203,7 @@ std::string item::save_info() * curammo isn't NULL. The crashes seem to occur most frequently when saving an * NPC, or when saving map data containing an item an NPC has dropped. */ - if (is_gun() && curammo != NULL) + if (curammo != NULL) ammotmp = curammo->id; if (ammotmp < 0 || ammotmp > num_items) ammotmp = 0; // Saves us from some bugs @@ -257,7 +258,7 @@ void item::load_info(std::string data, game *g) active = false; if (acttmp == 1) active = true; - if (is_gun() && ammotmp > 0) + if (ammotmp > 0) curammo = dynamic_cast(g->itypes[ammotmp]); else curammo = NULL; @@ -418,6 +419,15 @@ std::string item::info(bool showtext) else dump << " of " << ammo_name(tool->ammo) << "."; + } else if (is_style()) { + + dump << "\n"; + it_style* style = dynamic_cast(type); + for (int i = 0; i < style->moves.size(); i++) { + dump << default_technique_name(style->moves[i].tech) << + ". Requires Unarmed Skill of " << style->moves[i].level << "\n"; + } + } if (showtext) { @@ -443,7 +453,9 @@ nc_color item::color(player *u) { nc_color ret = c_ltgray; - if (is_gun()) { // Guns are green if you are carrying ammo for them + if (active) // Active items show up as yellow + ret = c_yellow; + else if (is_gun()) { // Guns are green if you are carrying ammo for them ammotype amtype = ammo_type(); if (u->has_ammo(amtype).size() > 0) ret = c_green; @@ -469,6 +481,16 @@ nc_color item::color(player *u) return ret; } +nc_color item::color_in_inventory(player *u) +{ +// Items in our inventory get colorized specially + nc_color ret = c_white; + if (active) + ret = c_yellow; + + return ret; +} + std::string item::tname(game *g) { std::stringstream ret; @@ -646,7 +668,8 @@ int item::volume_contained() int item::attack_time() { - return 65 + 4 * volume() + 2 * weight(); + int ret = 65 + 4 * volume() + 2 * weight(); + return ret; } int item::damage_bash() @@ -676,6 +699,29 @@ bool item::has_flag(item_flag f) return (type->item_flags & mfb(f)); } +bool item::has_technique(technique_id tech, player *p) +{ + if (is_style()) { + it_style *style = dynamic_cast(type); + for (int i = 0; i < style->moves.size(); i++) { + if (style->moves[i].tech == tech && + (p == NULL || p->sklevel[sk_unarmed] >= style->moves[i].level)) + return true; + } + } + return (type->techniques & mfb(tech)); +} + +std::vector item::techniques() +{ + std::vector ret; + for (int i = 0; i < NUM_TECHNIQUES; i++) { + if (has_technique( technique_id(i) )) + ret.push_back( technique_id(i) ); + } + return ret; +} + bool item::rotten(game *g) { if (!is_food() || g == NULL) @@ -758,6 +804,23 @@ int item::melee_value(int skills[num_skill_types]) return my_value; } + +style_move item::style_data(technique_id tech) +{ + style_move ret; + + if (!is_style()) + return ret; + + it_style* style = dynamic_cast(type); + + for (int i = 0; i < style->moves.size(); i++) { + if (style->moves[i].tech == tech) + return style->moves[i]; + } + + return ret; +} bool item::is_two_handed(player *u) { @@ -903,6 +966,11 @@ bool item::is_macguffin() return type->is_macguffin(); } +bool item::is_style() +{ + return type->is_style(); +} + bool item::is_other() { return (!is_gun() && !is_ammo() && !is_armor() && !is_food() && @@ -1222,3 +1290,28 @@ bool is_flammable(material m) { return (m == COTTON || m == WOOL || m == PAPER || m == WOOD || m == MNULL); } + +std::string default_technique_name(technique_id tech) +{ + switch (tech) { + case TEC_SWEEP: return "Sweep attack"; + case TEC_PRECISE: return "Precision attack"; + case TEC_BRUTAL: return "Knock-back attack"; + case TEC_GRAB: return "Grab"; + case TEC_WIDE: return "Hit all adjacent monsters"; + case TEC_RAPID: return "Rapid attack"; + case TEC_FEINT: return "Feint"; + case TEC_THROW: return "Throw"; + case TEC_BLOCK: return "Block"; + case TEC_BLOCK_LEGS: return "Leg block"; + case TEC_WBLOCK_1: return "Weak block"; + case TEC_WBLOCK_2: return "Parry"; + case TEC_WBLOCK_3: return "Shield"; + case TEC_COUNTER: return "Counter-attack"; + case TEC_BREAK: return "Grab break"; + case TEC_DEF_THROW: return "Defensive throw"; + case TEC_DEF_DISARM: return "Defense disarm"; + default: return "A BUG!"; + } + return "A BUG!"; +} diff --git a/item.h b/item.h index c9410eafbf..7216e1398b 100644 --- a/item.h +++ b/item.h @@ -25,6 +25,7 @@ class item item in_its_container(std::vector *itypes); nc_color color(player *u); + nc_color color_in_inventory(player *u); std::string tname(game *g = NULL); // g needed for rotten-test void use(player &u); bool burn(int amount = 1); // Returns true if destroyed @@ -60,6 +61,8 @@ class item int damage_bash(); int damage_cut(); bool has_flag(item_flag f); + bool has_technique(technique_id t, player *p = NULL); + std::vector techniques(); bool goes_bad(); bool count_by_charges(); bool craft_has_charges(); @@ -69,6 +72,8 @@ class item int weapon_value(int skills[num_skill_types]); // As above, but discounts its use as a ranged weapon int melee_value (int skills[num_skill_types]); +// Returns the data associated with tech, if we are an it_style + style_move style_data(technique_id tech); bool is_two_handed(player *u); bool made_of(material mat); bool conductive(); // Electricity @@ -93,6 +98,7 @@ class item bool is_tool(); bool is_software(); bool is_macguffin(); + bool is_style(); bool is_other(); // Doesn't belong in other categories bool is_artifact(); @@ -114,6 +120,7 @@ class item int mission_id;// Refers to a mission in game's master list int player_id; // Only give a mission to the right player! + }; diff --git a/itype.h b/itype.h index bca8be4990..3e41dd0ece 100644 --- a/itype.h +++ b/itype.h @@ -54,16 +54,22 @@ itm_wrapper, itm_syringe, itm_rag, itm_fur, itm_leather, itm_superglue, itm_id_science, itm_id_military, itm_electrohack, itm_string_6, itm_string_36, itm_rope_6, itm_rope_30, itm_chain, itm_processor, itm_RAM, itm_power_supply, itm_amplifier, itm_transponder, itm_receiver, itm_antenna, itm_steel_chunk, - itm_motor, itm_hose, itm_glass_sheet, itm_manhole_cover, itm_rock, itm_stick, - itm_broom, itm_mop, itm_screwdriver, itm_wrench, itm_saw, itm_hacksaw, - itm_hammer_sledge, itm_hatchet, itm_ax, itm_nailboard, itm_xacto, itm_scalpel, - itm_pot, itm_pan, itm_knife_butter, itm_knife_steak, itm_knife_butcher, - itm_knife_combat, itm_2x4, itm_muffler, itm_pipe, itm_bat, itm_machete, - itm_katana, itm_spear_wood, itm_spear_knife, itm_baton, itm_bee_sting, - itm_wasp_sting, itm_chitin_piece, itm_biollante_bud, itm_canister_empty, - itm_gold, itm_coal, itm_petrified_eye, itm_spiral_stone, itm_rapier, itm_cane, - itm_binoculars, itm_usb_drive, itm_pike, itm_broadsword, itm_mace, - itm_morningstar, itm_pool_cue, itm_pool_ball, itm_candlestick, + itm_steel_lump, itm_hose, itm_glass_sheet, itm_manhole_cover, itm_rock, + itm_stick, itm_broom, itm_mop, itm_screwdriver, itm_wrench, itm_saw, + itm_hacksaw, itm_hammer_sledge, itm_hatchet, itm_ax, itm_nailboard, itm_xacto, + itm_scalpel, itm_pot, itm_pan, itm_knife_butter, itm_knife_steak, + itm_knife_butcher, itm_knife_combat, itm_2x4, itm_muffler, itm_pipe, itm_bat, + itm_machete, itm_katana, itm_spear_wood, itm_spear_knife, itm_baton, + itm_bee_sting, itm_wasp_sting, itm_chitin_piece, itm_biollante_bud, + itm_canister_empty, itm_gold, itm_coal, itm_petrified_eye, itm_spiral_stone, + itm_rapier, itm_cane, itm_binoculars, itm_usb_drive, itm_pike, itm_broadsword, + itm_mace, itm_morningstar, itm_pool_cue, itm_pool_ball, itm_candlestick, +// Vehicle parts + itm_frame, itm_wheel, itm_big_wheel, itm_seat, itm_vehicle_controls, + itm_combustion_small, itm_combustion, itm_combustion_large, + itm_motor, itm_motor_large, itm_plasma_engine, + itm_metal_tank, itm_storage_battery, itm_minireactor, itm_solar_panel, + itm_steel_plate, itm_alloy_plate, itm_spiked_plate, itm_hard_plate, // Footwear itm_sneakers, itm_boots, itm_boots_steel, itm_boots_winter, itm_mocassins, itm_flip_flops, itm_dress_shoes, itm_heels, @@ -154,7 +160,7 @@ itm_lighter, itm_sewing_kit, itm_scissors, itm_hammer, itm_extinguisher, itm_mininuke_act, itm_pheromone, itm_portal, itm_bot_manhack, itm_bot_turret, itm_UPS_off, itm_UPS_on, itm_tazer, itm_mp3, itm_mp3_on, itm_vortex_stone, itm_dogfood, itm_boobytrap, itm_c4, itm_c4armed, itm_dog_whistle, - itm_vacutainer, + itm_vacutainer, itm_welder, // Bionics containers itm_bionics_battery, itm_bionics_power, itm_bionics_tools, itm_bionics_neuro, itm_bionics_sensory, itm_bionics_aquatic, @@ -172,6 +178,13 @@ num_items, // These shouldn't be counted among "normal" items; thus, they are outside the // bounds of num_items itm_bio_claws, itm_bio_fusion, itm_bio_blaster, +// Unarmed Combat Styles +itm_style_karate, itm_style_aikido, itm_style_judo, itm_style_tai_chi, + itm_style_capoeira, itm_style_krav_maga, itm_style_muay_thai, + itm_style_ninjutsu, itm_style_taekwando, itm_style_tiger, itm_style_crane, + itm_style_leopard, itm_style_snake, itm_style_dragon, itm_style_centipede, + itm_style_venom_snake, itm_style_scorpion, itm_style_lizard, itm_style_toad, + itm_style_zui_quan, num_all_items }; @@ -222,6 +235,9 @@ IF_RELOAD_AND_SHOOT, // Reloading and shooting is one action IF_FIRE_100, // Fires 100 rounds at once! (e.g. flamethrower) IF_GRENADE, // NPCs treat this as a grenade +IF_UNARMED_WEAPON, // Counts as an unarmed weapon +IF_NO_UNWIELD, // Impossible to unwield, e.g. bionic claws + IF_AMMO_FLAME, // Sets fire to terrain and monsters IF_AMMO_INCENDIARY, // Sparks explosive terrain IF_AMMO_EXPLOSIVE, // Small explosion @@ -236,6 +252,49 @@ IF_AMMO_STREAM, // Doesn't stop once it hits a monster NUM_ITEM_FLAGS }; +enum technique_id { +TEC_NULL, +// Offensive Techniques +TEC_SWEEP, // Crits may make your enemy fall & miss a turn +TEC_PRECISE, // Crits are painful and stun +TEC_BRUTAL, // Crits knock the target back +TEC_GRAB, // Hit may allow a second unarmed attack attempt +TEC_WIDE, // Attacks adjacent oppoents +TEC_RAPID, // Hits faster +TEC_FEINT, // Misses take less time +TEC_THROW, // Attacks may throw your opponent +TEC_DISARM, // Remove an NPC's weapon +// Defensive Techniques +TEC_BLOCK, // Block attacks, reducing them to 25% damage +TEC_BLOCK_LEGS, // Block attacks, but with your legs +TEC_WBLOCK_1, // Weapon block, poor chance -- e.g. pole +TEC_WBLOCK_2, // Weapon block, moderate chance -- weapon made for blocking +TEC_WBLOCK_3, // Weapon block, good chance -- shield +TEC_COUNTER, // Counter-attack on a block or dodge +TEC_BREAK, // Break from a grab +TEC_DEF_THROW, // Throw an enemy that attacks you +TEC_DEF_DISARM, // Disarm an enemy + +NUM_TECHNIQUES +}; + +struct style_move +{ + std::string name; + technique_id tech; + int level; + + style_move(std::string N, technique_id T, int L) : + name (N), tech (T), level (L) { }; + + style_move() + { + name = ""; + tech = TEC_NULL; + level = 0; + } +}; + // Returns the name of a category of ammo (e.g. "shot") std::string ammo_name(ammotype t); // Returns the default ammo for a category of ammo (e.g. "itm_00_shot") @@ -257,8 +316,8 @@ struct itype material m1; // Main material material m2; // Secondary material -- MNULL if made of just 1 thing - unsigned char volume; // Space taken up by this item - unsigned char weight; // Weight in quarter-pounds; is 64 lbs max ok? + unsigned int volume; // Space taken up by this item + unsigned int weight; // Weight in quarter-pounds; is 64 lbs max ok? // Also assumes positive weight. No helium, guys! signed char melee_dam; // Bonus for melee damage; may be a penalty @@ -266,6 +325,7 @@ struct itype signed char m_to_hit; // To-hit bonus for melee combat; -5 to 5 is reasonable unsigned item_flags : NUM_ITEM_FLAGS; + unsigned techniques : NUM_TECHNIQUES; virtual bool is_food() { return false; } virtual bool is_ammo() { return false; } @@ -278,6 +338,7 @@ struct itype virtual bool is_container() { return false; } virtual bool is_software() { return false; } virtual bool is_macguffin() { return false; } + virtual bool is_style() { return false; } virtual bool is_artifact() { return false; } virtual bool count_by_charges() { return false; } virtual std::string save_data() { return std::string(); } @@ -295,14 +356,15 @@ struct itype melee_dam = 0; m_to_hit = 0; item_flags = 0; + techniques = 0; } itype(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, - unsigned pitem_flags) { + unsigned pitem_flags, unsigned ptechniques = 0) { id = pid; rarity = prarity; price = pprice; @@ -318,6 +380,7 @@ struct itype melee_cut = pmelee_cut; m_to_hit = pm_to_hit; item_flags = pitem_flags; + techniques = ptechniques; } }; @@ -346,7 +409,7 @@ struct it_comest : public itype it_comest(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -388,7 +451,7 @@ struct it_ammo : public itype it_ammo(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -424,7 +487,7 @@ struct it_gun : public itype it_gun(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -460,7 +523,7 @@ struct it_gunmod : public itype it_gunmod(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -515,7 +578,7 @@ struct it_armor : public itype it_armor(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -548,7 +611,7 @@ struct it_book : public itype it_book(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -580,7 +643,7 @@ struct it_container : public itype it_container(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -620,7 +683,7 @@ struct it_tool : public itype it_tool(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -650,7 +713,7 @@ struct it_bionic : public itype it_bionic(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -677,7 +740,7 @@ struct it_macguffin : public itype it_macguffin(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -700,7 +763,7 @@ struct it_software : public itype it_software(int pid, unsigned char prarity, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, material pm2, - unsigned char pvolume, unsigned char pweight, + unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, @@ -712,6 +775,23 @@ struct it_software : public itype } }; +struct it_style : public itype +{ + virtual bool is_style() { return true; } + + std::vector moves; + + it_style(int pid, unsigned char prarity, unsigned int pprice, + std::string pname, std::string pdes, + char psym, nc_color pcolor, material pm1, material pm2, + unsigned char pvolume, unsigned char pweight, + signed char pmelee_dam, signed char pmelee_cut, + signed char pm_to_hit, unsigned pitem_flags) + +:itype(pid, prarity, pprice, pname, pdes, psym, pcolor, pm1, pm2, + pvolume, pweight, pmelee_dam, pmelee_cut, pm_to_hit, pitem_flags) { } +}; + struct it_artifact_tool : public it_tool { art_charge charge_type; @@ -764,7 +844,7 @@ struct it_artifact_tool : public it_tool it_artifact_tool(int pid, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, - material pm2, unsigned char pvolume, unsigned char pweight, + material pm2, unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags) @@ -813,7 +893,7 @@ struct it_artifact_armor : public it_armor it_artifact_armor(int pid, unsigned int pprice, std::string pname, std::string pdes, char psym, nc_color pcolor, material pm1, - material pm2, unsigned char pvolume, unsigned char pweight, + material pm2, unsigned short pvolume, unsigned short pweight, signed char pmelee_dam, signed char pmelee_cut, signed char pm_to_hit, unsigned pitem_flags, diff --git a/itypedef.cpp b/itypedef.cpp index 0941426833..9c8bf6a1a3 100644 --- a/itypedef.cpp +++ b/itypedef.cpp @@ -1,5 +1,6 @@ #include "itype.h" #include "game.h" +#include "setvector.h" #include // Armor colors @@ -14,6 +15,9 @@ #define C_STORE c_green #define C_DECOR c_ltgreen +// Special function for setting melee techniques +#define TECH(t) itypes[index]->techniques = t + // GENERAL GUIDELINES // When adding a new item, you MUST REMEMBER to insert it in the itype_id enum // at the top of itype.h! @@ -489,7 +493,7 @@ MED("Dayquil", 70, 75, c_yellow, itm_null, Daytime flu medication. Will halt all flu symptoms for a while."); MED("Nyquil", 70, 85, c_blue, itm_null, - PLASTIC, -7, 1, 0, 10, 0,&iuse::flusleep, ADD_NULL, "\ + PLASTIC, -7, 1, 20, 10, 0,&iuse::flusleep, ADD_SLEEP, "\ Nighttime flu medication. Will halt all flu symptoms for a while, plus make\n\ you sleepy."); @@ -646,8 +650,10 @@ MELEE("rope - 30 ft", 35,100, ',', c_yellow, WOOD, MNULL, A long nylon rope. Useful for keeping yourself safe from falls."); MELEE("steel chain", 20, 80, '/', c_cyan, STEEL, MNULL, - 4, 8, 12, 0, 3, mfb(IF_WRAP), "\ -A heavy steel chain. Useful as a weapon, or for crafting."); + 4, 8, 12, 0, 2, mfb(IF_WRAP), "\ +A heavy steel chain. Useful as a weapon, or for crafting. It has a chance\n\ +to wrap around your target, allowing for a bonus unarmed attack."); +TECH( mfb(TEC_GRAB) ); MELEE("processor board",15,120, ',', c_ltcyan, IRON, PLASTIC, 1, 0, -3, 0, -1, 0, "\ @@ -689,9 +695,11 @@ MELEE("chunk of steel", 30, 10, ',', c_ltblue, STEEL, MNULL, A misshapen chunk of steel. Makes a decent weapon in a pinch, and is also\n\ useful for some crafting recipes."); -MELEE("electric motor", 2,120, ',', c_ltcyan, IRON, MNULL, - 4, 6, 4, 0, 0, 0, "\ -A powerful electric motor. Useful for crafting."); +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("lump of steel", 30, 20, ',', c_ltblue, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 2, 80, 18, 0, -4, 0, "\ +A misshapen heavy piece of steel. Useful for some crafting recipes."); MELEE("rubber hose", 15, 80, ',', c_green, PLASTIC,MNULL, 3, 2, 4, 0, 3, mfb(IF_WRAP), "\ @@ -701,10 +709,11 @@ MELEE("sheet of glass", 5,135, ']', c_ltcyan, GLASS, MNULL, 50, 20, 16, 0, -5, 0, "\ A large sheet of glass. Easily shattered. Useful for re-paning windows."); -MELEE("manhole cover", 0, 20, ']', c_dkgray, IRON, MNULL, +MELEE("manhole cover", 1, 20, ']', c_dkgray, IRON, MNULL, 45,250, 20, 0,-10, 0, "\ A heavy iron disc which generally covers a ladder into the sewers. Lifting\n\ it from the manhole is impossible without a crowbar."); +TECH( mfb(TEC_WBLOCK_3) ); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("rock", 40, 0, '*', c_ltgray, STONE, MNULL, @@ -721,10 +730,12 @@ by fours for crafting."); MELEE("broom", 30, 24, '/', c_blue, PLASTIC,MNULL, 10, 8, 6, 0, 1, 0, "\ A long-handled broom. Makes a terrible weapon unless you're chasing cats."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("mop", 20, 28, '/', c_ltblue, PLASTIC,MNULL, 11, 12, 5, 0, -2, 0, "\ An unwieldy mop. Essentially useless."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("screwdriver", 40, 65, ';', c_ltcyan, IRON, PLASTIC, 1, 1, 2, 8, 1, mfb(IF_SPEAR), "\ @@ -747,9 +758,10 @@ MELEE("hack saw", 17, 65, ';', c_ltcyan, IRON, MNULL, A sturdy saw, useful for cutting through metal objects."); MELEE("sledge hammer", 6, 120,'/', c_brown, WOOD, IRON, - 18, 34, 40, 0, 0, 0, "\ + 18, 38, 40, 0, 0, 0, "\ A large, heavy hammer. Makes a good melee weapon for the very strong, but is\n\ nearly useless in the hands of the weak."); +TECH( mfb(TEC_BRUTAL)|mfb(TEC_WIDE) ); MELEE("hatchet", 10, 95,';', c_ltgray, IRON, WOOD, 6, 7, 12, 12, 1, 0, "\ @@ -769,12 +781,16 @@ mace. Makes a great melee weapon."); MELEE("X-Acto knife", 10, 40,';', c_dkgray, IRON, PLASTIC, 1, 0, 0, 14, -4, mfb(IF_SPEAR), "\ -A small, very sharp knife. Causes decent damage but is difficult to hit with." +A small, very sharp knife. Causes decent damage but is difficult to hit\n\ +with. Its small tip allows for a precision strike in hands of the skill." ); +TECH(mfb(TEC_PRECISE)); MELEE("scalpel", 48, 40,',', c_cyan, STEEL, MNULL, 1, 0, 0, 18, -4, mfb(IF_SPEAR), "\ -A small, very sharp knife, used in surgery."); +A small, very sharp knife, used in surgery. Its small tip allows for a\n\ +precision strike in the hands of the skilled."); +TECH(mfb(TEC_PRECISE)); MELEE("pot", 25, 45,')', c_ltgray, IRON, MNULL, 8, 6, 9, 0, 1, 0, "\ @@ -796,14 +812,14 @@ A sharp knife. Makes a poor melee weapon, but is decent at butchering\n\ corpses."); MELEE("butcher knife", 10, 80,';', c_cyan, STEEL, MNULL, - 3, 6, 4, 20, -3, 0, "\ + 3, 6, 4, 18, -3, 0, "\ A sharp, heavy knife. Makes a good melee weapon, and is the best item for\n\ butchering corpses."); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("combat knife", 14, 100,';', c_blue, STEEL, PLASTIC, // VOL WGT DAM CUT HIT FLAGS - 2, 2, 2, 22, -3, mfb(IF_STAB), "\ + 2, 2, 2, 22, -2, mfb(IF_STAB), "\ Designed for combat, and deadly in the right hands. Can be used to butcher\n\ corpses."); @@ -811,11 +827,13 @@ MELEE("two by four", 60, 80,'/', c_ltred, WOOD, MNULL, 6, 6, 14, 0, 1, 0, "\ A plank of wood. Makes a decent melee weapon, and can be used to board up\n\ doors and windows if you have a hammer and nails."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("muffler", 30, 30,'/', c_ltgray, IRON, MNULL, 20, 20, 19, 0, -3, 0, "\ A muffler from a car. Very unwieldy as a weapon. Useful in a few crafting\n\ recipes."); +TECH( mfb(TEC_WBLOCK_2) ); MELEE("pipe", 20, 75,'/', c_dkgray, STEEL, MNULL, 4, 10, 13, 0, 3, 0, "\ @@ -824,40 +842,48 @@ A steel pipe, makes a good melee weapon. Useful in a few crafting recipes."); MELEE("baseball bat", 60, 160,'/', c_ltred, WOOD, MNULL, 12, 10, 28, 0, 3, 0, "\ A sturdy wood bat. Makes a great melee weapon."); +TECH( mfb(TEC_WBLOCK_1) ); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("machete", 5, 280,'/', c_blue, IRON, MNULL, // VOL WGT DAM CUT HIT FLAGS 8, 14, 6, 28, 2, 0, "\ This huge iron knife makes an excellent melee weapon."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("katana", 2, 980,'/', c_ltblue, STEEL, MNULL, 16, 16, 4, 45, 1, mfb(IF_STAB), "\ A rare sword from Japan. Deadly against unarmored targets, and still very\n\ effective against the armored."); +TECH( mfb(TEC_RAPID)|mfb(TEC_WBLOCK_2) ); MELEE("wood spear", 5, 40,'/', c_ltred, WOOD, MNULL, 5, 3, 4, 18, 1, mfb(IF_SPEAR), "\ A simple wood pole with one end sharpened."); +TECH( mfb(TEC_WBLOCK_1) | mfb(TEC_RAPID) ); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("steel spear", 5, 140,'/', c_ltred, WOOD, STEEL, // VOL WGT DAM CUT HIT FLAGS 6, 6, 2, 28, 1, mfb(IF_SPEAR), "\ A simple wood pole made deadlier by the knife tied to it."); +TECH( mfb(TEC_WBLOCK_1) | mfb(TEC_RAPID) ); MELEE("expandable baton",8, 175,'/', c_blue, STEEL, MNULL, 1, 4, 12, 0, 2, 0, "\ A telescoping baton that collapses for easy storage. Makes an excellent\n\ melee weapon."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("bee sting", 5, 70,',', c_dkgray, FLESH, MNULL, - 1, 0, 0, 18, 1, mfb(IF_SPEAR), "\ -A four-inch stinger from a giant bee. Makes a good melee weapon."); + 1, 0, 0, 18, -1, mfb(IF_SPEAR), "\ +A six-inch stinger from a giant bee. Makes a good melee weapon."); +TECH( mfb(TEC_PRECISE) ); MELEE("wasp sting", 5, 90,',', c_dkgray, FLESH, MNULL, - 1, 0, 0, 22, 1, mfb(IF_SPEAR), "\ -A four-inch stinger from a giant wasp. Makes a good melee weapon."); + 1, 0, 0, 22, -1, mfb(IF_SPEAR), "\ +A six-inch stinger from a giant wasp. Makes a good melee weapon."); +TECH( mfb(TEC_PRECISE) ); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("chunk of chitin",10, 15,',', c_red, FLESH, MNULL, @@ -900,6 +926,7 @@ MELEE("rapier", 3, 980,'/', c_ltblue, STEEL, MNULL, 6, 9, 5, 28, 2, mfb(IF_STAB), "\ Preferred weapon of gentlemen and swashbucklers. Light and quick, it makes\n\ any battle a stylish battle."); +TECH( mfb(TEC_RAPID)|mfb(TEC_WBLOCK_1)|mfb(TEC_PRECISE) ); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("walking cane", 10, 160,'/', c_ltred, WOOD, MNULL, @@ -907,6 +934,7 @@ MELEE("walking cane", 10, 160,'/', c_ltred, WOOD, MNULL, 8, 7, 10, 0, 2, 0, "\ Handicapped or not, you always walk in style. Consisting of a metal\n\ headpiece and a wooden body, this makes a great bashing weapon in a pinch."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("binoculars", 20, 300,';', c_ltgray, PLASTIC,GLASS, 2, 3, 6, 0, -1, 0, "\ @@ -921,7 +949,7 @@ A USB thumb drive. Useful for holding software."); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("awl pike", 5,2000,'/', c_ltcyan, IRON, WOOD, // VOL WGT DAM CUT HIT FLAGS - 14, 18, 8, 60, 2, mfb(IF_SPEAR), "\ + 14, 18, 8, 50, 2, mfb(IF_SPEAR), "\ A medieval weapon consisting of a wood shaft, tipped with an iron spike.\n\ Though large and heavy compared to other spears, its accuracy and damage\n\ are unparalled."); @@ -931,16 +959,18 @@ MELEE("broadsword", 30,1200,'/',c_cyan, IRON, MNULL, An early modern sword seeing use in the 16th, 17th ane 18th centuries.\n\ Called 'broad' to contrast with the slimmer rapiers."); -MELEE("mace", 20,1000,'/',c_black, IRON, WOOD, +MELEE("mace", 20,1000,'/',c_dkgray, IRON, WOOD, 10, 18, 36, 0, 1, 0, "\ A medieval weapon consisting of a wood handle with a heavy iron end. It\n\ is heavy and slow, but its crushing damage is devastating."); +TECH( mfb(TEC_SWEEP) ); -MELEE("morningstar", 10,1200,'/',c_black, IRON, WOOD, +MELEE("morningstar", 10,1200,'/',c_dkgray, IRON, WOOD, 11, 20, 32, 4, 1, mfb(IF_SPEAR), "\ A medieval weapon consisting of a wood handle with a heavy, spiked iron\n\ ball on the end. It deals devastating crushing damage, with a small\n\ amount of piercing to boot."); +TECH( mfb(TEC_SWEEP) ); // NAME RAR PRC SYM COLOR MAT1 MAT2 MELEE("pool cue", 4, 80,'/', c_red, WOOD, MNULL, @@ -948,6 +978,7 @@ MELEE("pool cue", 4, 80,'/', c_red, WOOD, MNULL, 14, 5, 12, 0, 3, 0, "\ A hard-wood stick designed for hitting colorful balls around a felt\n\ table. Truly, the coolest of sports."); +TECH( mfb(TEC_WBLOCK_1) ); MELEE("pool ball", 40, 30,'*', c_blue, STONE, MNULL, 1, 3, 12, 0, -3, 0, "\ @@ -957,6 +988,126 @@ MELEE("candlestick", 20,100,'/', c_yellow, SILVER, MNULL, 1, 5, 12, 0, 1, 0, "\ A gold candlestick."); +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("steel frame", 25, 35, ',', c_cyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 60, 240, 20, 0, -5, 0, "\ +A large frame made of steel. Useful for crafting."); +TECH( mfb(TEC_WBLOCK_3) ); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("wheel", 15, 50, '0', c_dkgray, STEEL, PLASTIC, +// VOL WGT DAM CUT HIT FLAGS + 10, 80, 8, 0, -4, 0, "\ +A wheel, perhaps from some car."); +TECH( mfb(TEC_WBLOCK_3) ); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("large wheel", 6, 80, '0', c_dkgray, STEEL, PLASTIC, +// VOL WGT DAM CUT HIT FLAGS + 20, 200, 12, 0, -5, 0, "\ +A large wheel, from some big car."); +TECH( mfb(TEC_WBLOCK_3) ); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("seat", 8, 250, '0', c_red, PLASTIC, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 30, 80, 4, 0, -4, 0, "\ +A soft car seat covered with leather."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("vehicle controls", 3, 400, '$', c_ltcyan, PLASTIC, STEEL, +// VOL WGT DAM CUT HIT FLAGS + 12, 30, 2, 0, -4, 0, "\ +A set of various vehicle controls. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("1L combustion engine", 5, 150, ',', c_ltcyan, IRON, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 6, 160, 8, 0, -2, 0, "\ +A small, yet powerful 2-cylinder combustion engine. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("2.5L combustion engine", 4, 180, ',', c_ltcyan, IRON, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 14, 400, 12, 0, -3, 0, "\ +A powerful 4-cylinder combustion engine. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("6L combustion engine", 2, 250, ',', c_ltcyan, IRON, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 25, 1100, 15, 0, -5, 0, "\ +A large and very powerful 8-cylinder combustion engine. Useful for\n\ +crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("electric motor", 2,120, ',', c_ltcyan, IRON, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 6, 80, 4, 0, 0, 0, "\ +A powerful electric motor. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("large electric motor", 1,220, ',', c_ltcyan, IRON, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 15, 650, 9, 0, -3, 0, "\ +A large and very powerful electric motor. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("plasma engine", 1, 900, ',', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 12, 350, 7, 0, -2, 0, "\ +High technology engine, working on hydrgen fuel."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("metal tank", 10, 40, '{', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 18, 25, 3, 0, -2, 0, "\ +A metal tank for holding liquids. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("storage battery", 6, 80, ',', c_ltcyan, IRON, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 8, 220, 6, 0, -2, 0, "\ +A large storage battery. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("minireactor", 1, 900, ',', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 6, 250, 11, 0, -4, 0, "\ +A small portable plutonium reactor. Handle with great care!"); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("solar panel", 3, 900, '#', c_yellow, GLASS, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 12, 4, 1, 0, -4, 0, "\ +Electronic device which can convert solar radiation into electric\n\ +power. Useful for crafting."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("steel plating", 30, 120, ',', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 12, 600, 6, 0, -1, 0, "\ +A piece of armor plating made of steel."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("superalloy plating", 10, 185, ',', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 12, 350, 6, 0, -1, 0, "\ +A piece of armor plating made of sturdy superalloy."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("spiked plating", 15, 185, ',', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 14, 600, 6, 3, -1, 0, "\ +A piece of armor plating made of steel. It is covered by menacing\n\ +spikes."); + +// NAME RAR PRC SYM COLOR MAT1 MAT2 +MELEE("hard plating", 30, 160, ',', c_ltcyan, STEEL, MNULL, +// VOL WGT DAM CUT HIT FLAGS + 12, 1800, 6, 0, -1, 0, "\ +A piece of very thick armor plating made of steel."); + // ARMOR #define ARMOR(name,rarity,price,color,mat1,mat2,volume,wgt,dam,to_hit,\ encumber,dmg_resist,cut_resist,env,warmth,storage,covers,des)\ @@ -1294,24 +1445,29 @@ ARMOR("hard hat", 50, 125,C_HAT, PLASTIC, MNULL, 8, 4, 6, 0, 1, 4, 5, 0, 1, 0, mfb(bp_head), "\ A hard plastic hat worn in constructions sites. Excellent protection from\n\ cuts and percussion."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("bike helmet", 35, 140,C_HAT, PLASTIC, MNULL, 12, 2, 4, 0, 1, 8, 2, 0, 2, 0, mfb(bp_head), "\ A thick foam helmet. Designed to protect against percussion."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("skid lid", 30, 190,C_HAT, PLASTIC, IRON, 10, 5, 8, 0, 2, 6, 16, 0, 1, 0, mfb(bp_head), "\ A small metal helmet that covers the head and protects against cuts and\n\ percussion."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("baseball helmet",45, 195,C_HAT, PLASTIC, IRON, 14, 6, 7, -1, 2, 10, 10, 1, 1, 0, mfb(bp_head), "\ A hard plastic helmet which covers the head and ears. Designed to protect\n\ against a baseball to the head."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("army helmet", 40, 480,C_HAT, PLASTIC, IRON, 16, 8, 10, -1, 2, 12, 28, 0, 2, 0, mfb(bp_head), "\ A heavy helmet whic provides excellent protection from all sorts of damage."); +TECH( mfb(TEC_WBLOCK_1) ); // NAME RAR PRC COLOR MAT1 MAT2 ARMOR("riot helmet", 25, 420,C_HAT, PLASTIC, IRON, @@ -1319,11 +1475,13 @@ ARMOR("riot helmet", 25, 420,C_HAT, PLASTIC, IRON, 20, 7, 8, -1, 2, 6, 28, 2, 2, 0, mfb(bp_head)|mfb(bp_eyes)| mfb(bp_mouth), "\ A helmet with a plastic shield that covers your entire face."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("motorcycle helmet",40,325,C_HAT, PLASTIC, IRON, 24, 8, 7, -1, 3, 8, 20, 1, 3, 0, mfb(bp_head)|mfb(bp_mouth), "\ A helmet with covers your head and chin, leaving space in between for you to\n\ wear goggles."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("chitinous helmet", 1, 380,C_HAT, FLESH, MNULL, 22, 1, 2, -2, 4, 10, 14, 4, 3, 0, mfb(bp_head)|mfb(bp_eyes)| @@ -1338,6 +1496,7 @@ ARMOR("great helm", 1,400,C_HAT, IRON, MNULL, mfb(bp_mouth), "\ A medieval helmet which provides excellent protection to the entire head, at\n\ the cost of great encumbrance."); +TECH( mfb(TEC_WBLOCK_1) ); ARMOR("top hat", 10, 55,C_HAT, PLASTIC, MNULL, 2, 1, -5, 0, 0, 0, 1, 1, 1, 0, mfb(bp_head), "\ @@ -1422,22 +1581,22 @@ A box of small steel balls. They deal virtually no damage.", 0); // NAME RAR PRC TYPE COLOR MAT -AMMO("wood arrow", 5,100,AT_ARROW, c_green, WOOD, +AMMO("wood arrow", 7,100,AT_ARROW, c_green, WOOD, // VOL WGT DMG AP RNG ACC REC COUNT - 2, 60, 16, 1, 10, 14, 0, 15, "\ + 2, 60, 8, 1, 10, 18, 0, 10, "\ A sharpened arrow carved from wood. It's light-weight, does little damage,\n\ and is so-so on accuracy. Stands a good chance of remaining intact once\n\ fired.", 0); AMMO("carbon fiber arrow",5,300,AT_ARROW, c_green, PLASTIC, - 2, 30, 24, 2, 15, 18, 0, 12, "\ + 2, 30, 12, 2, 15, 14, 0, 8, "\ High-tech carbon fiber shafts and 100 grain broadheads. Very light weight,\n\ fast, and notoriously fragile.", 0); AMMO("wood crossbow bolt",8,100,AT_BOLT, c_green, WOOD, - 1, 40, 16, 1, 10, 16, 0, 15, "\ + 1, 40, 10, 1, 10, 16, 0, 15, "\ A sharpened bolt carved from wood. It's lighter than steel bolts, and does\n\ less damage and is less accurate. Stands a good chance of remaining intact\n\ once fired.", @@ -1446,7 +1605,7 @@ once fired.", // NAME RAR PRC TYPE COLOR MAT AMMO("steel crossbow bolt",7,400,AT_BOLT, c_green, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 1, 90, 26, 3, 14, 12, 0, 10, "\ + 1, 90, 20, 3, 14, 12, 0, 10, "\ A sharp bolt made from steel. Deadly in skilled hands. Stands an excellent\n\ chance of remaining intact once fired.", 0); @@ -1466,7 +1625,7 @@ it very accurate at short range. Favored by SWAT forces.", // NAME RAR PRC TYPE COLOR MAT AMMO("shotgun slug", 6, 900,AT_SHOT, c_red, PLASTIC, // VOL WGT DMG AP RNG ACC REC COUNT - 2, 34, 50, 4, 12, 10, 28, 25, "\ + 2, 34, 50, 8, 12, 10, 28, 25, "\ A heavy metal slug used with shotguns to give them the range capabilities of\n\ a rifle. Extremely damaging but rather innaccurate. Works best in a shotgun\n\ with a rifled barrel.", @@ -1504,14 +1663,14 @@ short range and is unable to injure all but the smallest creatures.", // NAME RAR PRC TYPE COLOR MAT AMMO("9mm", 8, 300,AT_9MM, c_ltblue, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 2, 7, 18, 1, 14, 16, 13, 50, "\ + 2, 7, 18, 2, 14, 16, 13, 50, "\ 9 millimeter parabellum is generally regarded as the most popular handgun\n\ cartridge, used by the majority of US police forces. It is also a very\n\ popular round in sub-machine guns.", 0); AMMO("9mm +P", 8, 380,AT_9MM, c_ltblue, STEEL, - 1, 7, 20, 2, 14, 15, 14, 25, "\ + 1, 7, 20, 4, 14, 15, 14, 25, "\ Attempts to improve the ballistics of 9mm ammunition lead to high pressure\n\ rounds. Increased velocity resullts in superior accuracy and damage.", 0); @@ -1519,7 +1678,7 @@ rounds. Increased velocity resullts in superior accuracy and damage.", // NAME RAR PRC TYPE COLOR MAT AMMO("9mm +P+", 8, 440,AT_9MM, c_ltblue, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 1, 7, 22, 6, 16, 14, 15, 10, "\ + 1, 7, 22, 12, 16, 14, 15, 10, "\ A step beyond the high-pressure 9mm +P round, the +P+ is a very high pressure\n\ loading which offers a degree of armor-penetrating ability.", 0); @@ -1531,14 +1690,14 @@ throughout the 20th century. It is most commonly used in revolvers.", 0); AMMO(".38 Super", 7, 450,AT_38, c_ltblue, STEEL, - 1, 9, 25, 2, 16, 14, 14, 25, "\ + 1, 9, 25, 4, 16, 14, 14, 25, "\ The .38 Super is a high-pressure load of the .38 Special caliber. It is a\n\ popular choice in pistol competions for its high accuracy, while its stopping\n\ power keeps it popular for self-defense.", 0); AMMO("10mm Auto", 4, 420,AT_40, c_blue, STEEL, - 2, 9, 26, 5, 14, 18, 20, 50, "\ + 2, 9, 26, 10, 14, 18, 20, 50, "\ Originally used by the FBI, the organization eventually abandoned the round\n\ due to its high recoil. Although respected for its versatility and power, it\n\ has largely been supplanted by the downgraded .40 S&W.", @@ -1547,28 +1706,28 @@ has largely been supplanted by the downgraded .40 S&W.", // NAME RAR PRC TYPE COLOR MAT AMMO(".40 S&W", 7, 450,AT_40, c_blue, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 2, 9, 22, 1, 14, 16, 16, 50, "\ + 2, 9, 22, 2, 14, 15, 16, 50, "\ The .40 Smith & Wesson round was developed as an alternative to 10mm Auto for\n\ the FBI after they complained of high recoil. It is as accurate as 9mm, but\n\ has greater stopping power, leading to widespread use in law enforcement.", 0); AMMO(".44 Magnum", 7, 580,AT_44, c_blue, STEEL, - 2, 15, 36, 1, 16, 16, 22, 50, "\ + 2, 15, 36, 2, 16, 16, 22, 50, "\ Described (in 1971) by Dirty Harry as \"the most powerful handgun in the\n\ world,\" the .44 Magnum gained widespead popularity due to its depictions in\n\ the media. In reality, its intense recoil makes it unsuitable in most cases.", 0); AMMO(".45 ACP", 7, 470,AT_45, c_blue, STEEL, - 2, 10, 32, 1, 16, 18, 18, 50, "\ + 2, 10, 32, 2, 16, 18, 18, 50, "\ The .45 round was one of the most popular and powerful handgun rounds through\n\ the 20th century. It features very good accuracy and stopping power, but\n\ suffers from moderate recoil and poor armor penetration.", 0); AMMO(".45 FMJ", 4, 480,AT_45, c_blue, STEEL, - 1, 13, 26, 8, 16, 18, 18, 25, "\ + 1, 13, 26, 20, 16, 18, 18, 25, "\ Full Metal Jacket .45 rounds are designed to overcome the poor armor\n\ penetration of the standard ACP round. However, they are less likely to\n\ expand upon impact, resulting in reduced damage overall.", @@ -1577,21 +1736,21 @@ expand upon impact, resulting in reduced damage overall.", // NAME RAR PRC TYPE COLOR MAT AMMO(".45 Super", 5, 520,AT_45, c_blue, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 1, 11, 34, 4, 18, 16, 20, 10, "\ + 1, 11, 34, 8, 18, 16, 20, 10, "\ The .45 Super round is an updated variant of .45 ACP. It is overloaded,\n\ resulting in a great increase in muzzle velocity. This translates to higher\n\ accuracy and range, a minor armor piercing capability, and greater recoil.", 0); AMMO("5.7x28mm", 3, 500,AT_57, c_dkgray, STEEL, - 3, 2, 14, 15, 12, 12, 6, 100, "\ + 3, 2, 14, 30, 12, 12, 6, 100, "\ The 5.7x28mm round is a proprietary round developed by FN Hestal for use in\n\ their P90 SMG. While it is a very small round, comparable in power to .22,\n\ it features incredible armor-piercing capabilities and very low recoil.", 0); AMMO("4.6x30mm", 2, 520,AT_46, c_dkgray, STEEL, - 3, 1, 13, 18, 12, 12, 6, 100, "\ + 3, 1, 13, 35, 12, 12, 6, 100, "\ Designed by Heckler & Koch to compete with the 5.7x28mm round, 4.6x30mm is,\n\ like the 5.7, designed to minimize weight and recoil while increasing\n\ penetration of body armor. Its low recoil makes it ideal for automatic fire.", @@ -1600,7 +1759,7 @@ penetration of body armor. Its low recoil makes it ideal for automatic fire.", // NAME RAR PRC TYPE COLOR MAT AMMO("7.62x39mm M43", 6, 500,AT_762, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 3, 7, 25, 4, 20, 19, 24, 80, "\ + 3, 7, 25, 8, 20, 19, 24, 80, "\ Designed during World War II by the Soviet Union, the popularity of the AK-47\n\ and the SKS contributed to the widespread adaption of the 7.62x39mm rifle\n\ round. However, due to its lack of yaw, this round deals less damage than most." @@ -1608,14 +1767,14 @@ round. However, due to its lack of yaw, this round deals less damage than most." 0); AMMO("7.62x39mm M67", 7, 650,AT_762, c_dkgray, STEEL, - 3, 8, 28, 5, 20, 17, 25, 80, "\ + 3, 8, 28, 10, 20, 17, 25, 80, "\ The M67 variant of the popular 7.62x39mm rifle round was designed to improve\n\ yaw. This causes the round to tumble inside a target, causing significantly\n\ more damage. It is still outdone by shattering rounds.", 0); AMMO(".223 Remington", 8, 620,AT_223, c_dkgray, STEEL, - 2, 2, 36, 1, 24, 13, 30, 40, "\ + 2, 2, 36, 2, 24, 13, 30, 40, "\ The .223 rifle round is a civilian variant of the 5.56 NATO round. It is\n\ designed to tumble or fragment inside a target, dealing devastating damage.\n\ The lower pressure of the .223 compared to the 5.56 results in lower accuracy." @@ -1625,27 +1784,27 @@ The lower pressure of the .223 compared to the 5.56 results in lower accuracy." // NAME RAR PRC TYPE COLOR MAT AMMO("5.56 NATO", 6, 650,AT_223, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 2, 4, 40, 2, 25, 10, 32, 40, "\ + 2, 4, 40, 8, 25, 10, 32, 40, "\ This rifle round has enjoyed widespread use in NATO countries, thanks to its\n\ very light weight and high damage. It is designed to shatter inside a\n\ target, inflicting massive damage.", 0); AMMO("5.56 incendiary", 2, 840,AT_223, c_dkgray, STEEL, - 2, 4, 28, 7, 25, 11, 32, 30, "\ + 2, 4, 28, 18, 25, 11, 32, 30, "\ A variant of the widely-used 5.56 NATO round, incendiary rounds are designed\n\ to burn hotly upon impact, piercing armor and igniting flammable substances.", mfb(IF_AMMO_INCENDIARY)); AMMO(".270 Winchester", 8, 600,AT_3006, c_dkgray, STEEL, - 1, 7, 42, 2, 40, 12, 34, 20, "\ + 1, 7, 42, 4, 40, 12, 34, 20, "\ Based off the military .30-03 round, the .270 rifle round is compatible with\n\ most guns that fire .30-06 rounds. However, it is designed for hunting, and\n\ is less powerful than the military rounds, with nearly no armor penetration.", 0); AMMO(".30-06 AP", 4, 650,AT_3006, c_dkgray, STEEL, - 1, 12, 50, 16, 40, 7, 36, 10, "\ + 1, 12, 50, 30, 40, 7, 36, 10, "\ The .30-06 is a very powerful rifle round designed for long-range use. Its\n\ stupendous accuracy and armor piercing capabilities make it one of the most\n\ deadly rounds available, offset only by its drastic recoil and noise.", @@ -1654,20 +1813,20 @@ deadly rounds available, offset only by its drastic recoil and noise.", // NAME RAR PRC TYPE COLOR MAT AMMO(".30-06 incendiary", 1, 780,AT_3006, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 1, 12, 35, 20, 40, 8, 35, 5, "\ + 1, 12, 35, 50, 40, 8, 35, 5, "\ A variant of the powerful .30-06 sniper round, incendiary rounds are designed\n\ to burn hotly upon impact, piercing armor and igniting flammable substances.", mfb(IF_AMMO_INCENDIARY)); AMMO(".308 Winchester", 7, 620,AT_308, c_dkgray, STEEL, - 1, 9, 36, 1, 35, 7, 33, 20, "\ + 1, 9, 36, 2, 35, 7, 33, 20, "\ The .308 Winchester is a rifle round, the commercial equivalent of the\n\ military 7.62x51mm round. Its high accuracy and phenominal damage have made\n\ it the most poplar hunting round in the world.", 0); AMMO("7.62x51mm", 6, 680,AT_308, c_dkgray, STEEL, - 1, 9, 44, 4, 35, 6, 34, 20, "\ + 1, 9, 44, 8, 35, 6, 34, 20, "\ The 7.62x51mm largely replaced the .30-06 round as the standard military\n\ rifle round. It is lighter, but offers similar velocities, resulting in\n\ greater accuracy and reduced recoil.", @@ -1676,13 +1835,13 @@ greater accuracy and reduced recoil.", // NAME RAR PRC TYPE COLOR MAT AMMO("7.62x51mm incendiary",6, 740,AT_308, c_dkgray, STEEL, // VOL WGT DMG AP RNG ACC REC COUNT - 1, 9, 30, 12, 32, 6, 32, 10, "\ + 1, 9, 30, 25, 32, 6, 32, 10, "\ A variant of the powerful 7.62x51mm round, incendiary rounds are designed\n\ to burn hotly upon impact, piercing armor and igniting flammable substances.", mfb(IF_AMMO_INCENDIARY)); AMMO("fusion pack", 2, 800,AT_FUSION, c_ltgreen, PLASTIC, - 1, 2, 12, 6, 20, 4, 0, 20, "\ + 1, 2, 12, 15, 20, 4, 0, 20, "\ In the middle of the 21st Century, military powers began to look towards\n\ energy based weapons. The result was the standard fusion pack, capable of\n\ delivering bolts of superheated gas at near light speed with no recoil.", @@ -1726,13 +1885,13 @@ and sound, designed to blind, deafen, and disorient anyone nearby.", mfb(IF_AMMO_FLASHBANG)); AMMO("H&K 12mm", 2, 500,AT_12MM, c_red, STEEL, - 1, 10, 25, 6, 35, 9, 7, 20, "\ + 1, 10, 25, 12, 35, 9, 7, 20, "\ The Heckler & Koch 12mm projectiles are used in H&K railguns. It's made of a\n\ ferromagnetic metal, probably cobalt.", 0); AMMO("hydrogen", 8, 800,AT_PLASMA, c_green, STEEL, - 10, 25, 35, 3, 8, 4, 0, 25, "\ + 10, 25, 35, 14, 8, 4, 0, 25, "\ A canister of hydrogen. With proper equipment, it could be heated to plasma.", mfb(IF_AMMO_INCENDIARY)); @@ -1787,7 +1946,7 @@ mfb(IF_STR_RELOAD)); // NAME RAR PRC COLOR MAT1 MAT2 GUN("compound bow", 2,1400,c_yellow, STEEL, PLASTIC, // SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD - sk_archery, AT_ARROW,12, 8, 8, 1, 0, 16, 0, 6, 0, 1, 100, "\ + sk_archery, AT_ARROW,12, 8, 8, 1, 0, 20, 0, 6, 0, 1, 100, "\ A bow with wheels that fires high velocity arrows. Weaker people can use\n\ compound bows more easily. Arrows fired from this weapon have a good chance\n\ of remaining intact for re-use.", @@ -2043,12 +2202,12 @@ ammunition, it is designed for burst fire.", // NAME RAR PRC COLOR MAT1 MAT2 GUN("Marlin 39A", 14,1600,c_brown,IRON, WOOD, -// SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP - sk_rifle, AT_22, 11, 26, 12, 3, 3, 10, -5, 8, 0, 10, 450, "\ +// SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD + sk_rifle, AT_22, 11, 26, 12, 3, 3, 10, -5, 8, 0, 19, 90, "\ The oldest and longest-produced shoulder firearm in the world. Though it\n\ fires the weak .22 round, it is highly accurate and damaging, and essentially\n\ has no recoil.", -0); +mfb(IF_RELOAD_ONE)); GUN("Ruger 10/22", 12,1650,c_brown,IRON, WOOD, sk_rifle, AT_22, 11, 23, 12, 3, 0, 8, -5, 8, 0, 10, 500, "\ @@ -2108,7 +2267,9 @@ accurate, and uses the much-lighter .223 round, allowing for a higher ammo\n\ capacity.", 0); +// NAME RAR PRC COLOR MAT1 MAT2 GUN("AK-47", 16,4000,c_blue, IRON, WOOD, +// SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP sk_rifle, AT_762, 16, 38, 14, 2, 0, 11, 4, 9, 4, 30, 475, "\ One of the most recognizable assault rifles ever made, the AK-47 is renowned\n\ for its durability even under the worst conditions.", @@ -2167,6 +2328,7 @@ low recoil and high accuracy.", 0); GUN("M249", 1,7500,c_ltred,STEEL, PLASTIC, +// SKILL AMMO VOL WGT MDG HIT DMG ACC REC DUR BST CLIP RELOAD sk_rifle, AT_223, 32, 68, 27, -4, -6, 20, 6, 7, 20,200, 750, "\ The M249 is a mountable machine gun used by the US Military and SWAT teams.\n\ Quite innaccurate and difficult to control, the M249 is designed to fire many\n\ @@ -2443,9 +2605,9 @@ its normal rounds, or with a single 40MM round.", 0); // NAME RAR PRC COLOR MAT1 MAT2 VOL WGT DAM CUT HIT -GUNMOD("bayonet", 6, 400, c_ltcyan, STEEL, MNULL, 2, 2, 0, 20, -2, +GUNMOD("bayonet", 6, 400, c_ltcyan, STEEL, MNULL, 2, 2, 0, 16, -3, // ACC DAM NOI CLP REC BST NEWTYPE PISTOL SHOT SMG RIFLE - 0, 0, 0, 0, 2, 0, AT_NULL, false, true, true, true, + 0, 0, 0, 0, 3, 0, AT_NULL, false, true, true, true, 0, "\ A bayonet is a stabbing weapon which can be attached to the front of a\n\ shotgun, sub-machinegun or rifle, allowing a melee attack to deal\n\ @@ -2482,8 +2644,8 @@ BOOK("TIME magazine", 35, 40,c_pink, PAPER, MNULL, Current events concerning a bunch of people who're all (un)dead now."); BOOK("Top Gear magazine", 40, 45,c_pink, PAPER, MNULL, - 1, 1, -3, 1, sk_mechanics, 1, 0, 1, 2, 8, "\ -Lots of articles about cars and mechanics. You might learn a little."); + 1, 1, -3, 1, sk_driving, 1, 0, 1, 2, 8, "\ +Lots of articles about cars and driving techniques."); BOOK("Bon Appetit", 30, 45,c_pink, PAPER, MNULL, 1, 1, -3, 1, sk_cooking, 1, 0, 1, 5, 8, "\ @@ -2759,6 +2921,7 @@ TOOL("chainsaw (off)", 7, 350,'/', c_red, IRON, PLASTIC, 12, 40, 10, 0, -4, 400, 0, 0, 0, AT_GAS, itm_null, &iuse::chainsaw_off,0, "Using this item will, if loaded with gas, cause it to turn on, making a very\n\ powerful, but slow, unwieldy, and noisy, melee weapon."); +TECH( mfb(TEC_SWEEP) ); // NAME RAR VAL SYM COLOR MAT1 MAT TOOL("chainsaw (on)", 0, 350,'/', c_red, IRON, PLASTIC, @@ -2767,6 +2930,7 @@ TOOL("chainsaw (on)", 0, 350,'/', c_red, IRON, PLASTIC, &iuse::chainsaw_on, mfb(IF_MESSY), "\ This chainsaw is on, and is continuously draining gasoline. Use it to turn\n\ it off."); +TECH( mfb(TEC_SWEEP) ); TOOL("jackhammer", 2, 890,';', c_magenta, IRON, MNULL, 13, 54, 20, 6, -4, 120, 0,10, 0, AT_GAS, itm_null, &iuse::jackhammer,0,"\ @@ -3064,6 +3228,13 @@ A tool for drawing blood, including a vacuum-sealed test tube for holding the\n\ sample. Use this tool to draw blood, either from yourself or from a corpse\n\ you are standing on."); +// NAME RAR PRC SYM COLOR MAT1 MAT +TOOL("welder", 10,900,';', c_ltred, IRON,MNULL, +// VOL WGT DAM CUT HIT MAX DEF USE SEC FUEL REVERT FUNCTION + 6, 24, 7, 0, -1, 1000, 300, 50, 0, AT_BATT, itm_null, &iuse::none, +0, "\ +A tool for welding metal pieces together. Useful for construction."); + // BIONICS // These are the modules used to install new bionics in the player. They're // very simple and straightforward; a difficulty, followed by a NULL-terminated @@ -3215,7 +3386,8 @@ MELEE("Null 2 - num_items",0,0,'#',c_white,MNULL,MNULL,0,0,0,0,0,0,""); // NAME RARE SYM COLOR MAT1 MAT2 MELEE("adamantite claws",0,0,'{', c_pink, STEEL, MNULL, // VOL WGT DAM CUT HIT - 2, 0, 8, 16, 4, mfb(IF_STAB), "\ + 2, 0, 8, 16, 4, + mfb(IF_STAB)|mfb(IF_UNARMED_WEAPON)|mfb(IF_NO_UNWIELD), "\ Short and sharp claws made from a high-tech metal."); // NAME RARE TYPE COLOR MAT @@ -3230,6 +3402,234 @@ GUN("fusion blaster", 0,0,c_magenta, STEEL, PLASTIC, "",0); +// Unarmed Styles + +#define STYLE(name, dam, description, ...) \ +index++; \ +itypes.push_back(new it_style(index, 0, 0, name, description, '$', \ + c_white, MNULL, MNULL, 0, 0, dam, 0, 0, 0)); \ +setvector((static_cast(itypes[index]))->moves, __VA_ARGS__, NULL); \ +itypes[index]->item_flags |= mfb(IF_UNARMED_WEAPON) + +/* +#define STYLE(name, description)\ +index++; \ +itypes.push_back(new it_style(index, 0, 0, name, description, '$', \ + c_white, MNULL, MNULL, 0, 0, 0, 0, 0, 0)) + +STYLE("karate", "karate is cool"); +*/ + +STYLE("karate", 2, "\ +Karate is a popular martial art, originating from Japan. It focuses on\n\ +rapid, precise attacks, blocks, and fluid movement. A successful hit allows\n\ +you an extra dodge and two extra blocks on the following round.", + +"quickly punch", TEC_RAPID, 0, +"block", TEC_BLOCK, 2, +"karate chop", TEC_PRECISE, 4 +); + +STYLE("aikido", 0, "\ +Aikido is a Japanese martial art focused on self-defense, while minimizing\n\ +injury to the attacker. It uses defense throws and disarms. Damage done\n\ +while using this technique is halved, but pain inflicted is doubled.", + +"feint at", TEC_FEINT, 2, +"throw", TEC_DEF_THROW, 2, +"disarm", TEC_DISARM, 3, +"disarm", TEC_DEF_DISARM, 4 +); + +STYLE("judo", 0, "\ +Judo is a martial art which focuses on grabs and throws, both defensive and\n\ +offensive. It also focuses on recovering from throws; while using judo, you\n\ +will not lose any turns to being thrown or knocked down.", + +"grab", TEC_GRAB, 2, +"throw", TEC_THROW, 3, +"throw", TEC_DEF_THROW, 4 +); + +STYLE("tai chi", 0, "\ +Though tai chi is often seen as a form of mental and physical exercise, it is\n\ +a legitimate martial art, focused on self-defense. Its ability to absorb the\n\ +force of an attack makes your Perception decrease damage further on a block.", + +"block", TEC_BLOCK, 1, +"disarm", TEC_DEF_DISARM, 3, +"strike", TEC_PRECISE, 4 +); + +STYLE("capoeira", 1, "\ +A dance-like style with its roots in Brazilian slavery, capoeira is focused\n\ +on fluid movement and sweeping kicks. Moving a tile will boost attack and\n\ +dodge; attacking boosts dodge, and dodging boosts attack.", + +"bluff", TEC_FEINT, 1, +"low kick", TEC_SWEEP, 3, +"spin and hit", TEC_COUNTER, 4, +"spin-kick", TEC_WIDE, 5 +); + +STYLE("krav maga", 4, "\ +Originating in Israel, Krav Maga is based on taking down an enemy quickly and\n\ +effectively. It focuses on applicable attacks rather than showy or complex\n\ +moves. Popular among police and armed forces everywhere.", + +"quickly punch", TEC_RAPID, 2, +"block", TEC_BLOCK, 2, +"feint at", TEC_FEINT, 3, +"jab", TEC_PRECISE, 3, +"disarm", TEC_DISARM, 3, +"block", TEC_BLOCK_LEGS, 4, +"counter-attack", TEC_COUNTER, 4, +"disarm", TEC_DEF_DISARM, 4, +"", TEC_BREAK, 4, +"grab", TEC_GRAB, 5 +); + +STYLE("muay thai", 4, "\ +Also referred to as the \"Art of 8 Limbs,\" Muay Thai is a popular fighting\n\ +technique from Thailand which uses powerful strikes. It does extra damage\n\ +against large or strong opponents.", + +"slap", TEC_RAPID, 2, +"block", TEC_BLOCK, 3, +"block", TEC_BLOCK_LEGS, 4, +"power-kick", TEC_BRUTAL, 4, +"counter-attack", TEC_COUNTER, 5 +); + +STYLE("ninjutsu", 1, "\ +Ninjutsu is a martial art and set of tactics used by ninja in feudal Japan.\n\ +It focuses on rapid, precise, silent strikes. Ninjutsu is entirely silent.\n\ +It also provides small combat bonuses the turn after moving a tile.", + +"quickly punch", TEC_RAPID, 3, +"jab", TEC_PRECISE, 4, +"block", TEC_BLOCK, 4 +); + +STYLE("taekwondo", 2, "\ +Taekwondo is the national sport of Korea, and was used by the South Korean\n\ +army in the 20th century. Focused on kicks and punches, it also includes\n\ +strength training; your blocks absorb extra damage the stronger you are.", + +"block", TEC_BLOCK, 2, +"block", TEC_BLOCK_LEGS, 3, +"jab", TEC_PRECISE, 4, +"brutally kick", TEC_BRUTAL, 4, +"spin-kick", TEC_SWEEP, 5 +); + +STYLE("tiger style", 4, "\ +One of the five Shaolin animal styles. Tiger style focuses on relentless\n\ +attacks above all else. Strength, not Dexterity, is used to determine hits;\n\ +you also receive an accumulating bonus for several turns of sustained attack.", + +"grab", TEC_GRAB, 4 +); + +STYLE("crane style", 0, "\ +One of the five Shaolin animal styles. Crane style uses intricate hand\n\ +techniques and jumping dodges. Dexterity, not Strength, is used to determine\n\ +damage; you also receive a dodge bonus the turn after moving a tile.", + +"feint at", TEC_FEINT, 2, +"block", TEC_BLOCK, 3, +"", TEC_BREAK, 3, +"hand-peck", TEC_PRECISE, 4 +); + +STYLE("leopard style", 3, "\ +One of the five Shaolin animal styles. Leopard style focuses on rapid,\n\ +strategic strikes. Your Perception and Intelligence boost your accuracy, and\n\ +moving a single tile provides an increased boost for one turn.", + +"swiftly jab", TEC_RAPID, 2, +"counter-attack", TEC_COUNTER, 4, +"leopard fist", TEC_PRECISE, 5 +); + +STYLE("snake style", 1, "\ +One of the five Shaolin animal styles. Snake style uses sinuous movement and\n\ +precision strikes. Perception increases your chance to hit as well as the\n\ +damage you deal.", + +"swiftly jab", TEC_RAPID, 2, +"feint at", TEC_FEINT, 3, +"snakebite", TEC_PRECISE, 4, +"writhe free from", TEC_BREAK, 4 +); + +STYLE("dragon style", 2, "\ +One of the five Shaolin animal styles. Dragon style uses fluid movements and\n\ +hard strikes. Intelligence increases your chance to hit as well as the\n\ +damage you deal. Moving a tile will boost damage further for one turn.", + +"", TEC_BLOCK, 2, +"grab", TEC_GRAB, 4, +"counter-attack", TEC_COUNTER, 4, +"spin-kick", TEC_SWEEP, 5, +"dragon strike", TEC_BRUTAL, 6 +); + +STYLE("centipede style", 0, "\ +One of the Five Deadly Venoms. Centipede style uses an onslaught of rapid\n\ +strikes. Every strike you make reduces the movement cost of attacking by 4;\n\ +this is cumulative, but is reset entirely if you are hit even once.", + +"swiftly hit", TEC_RAPID, 2, +"block", TEC_BLOCK, 3 +); + +STYLE("viper style", 2, "\ +One of the Five Deadly Venoms. Viper Style has a unique three-hit combo; if\n\ +you score a critical hit, it is initiated. The second hit uses a coned hand\n\ +to deal piercing damage, and the 3rd uses both hands in a devastating strike.", +"", TEC_RAPID, 3, + +"feint at", TEC_FEINT, 3, +"writhe free from", TEC_BREAK, 4 +); + +STYLE("scorpion style", 3, "\ +One of the Five Deadly Venoms. Scorpion Style is a mysterious art which uses\n\ +pincer-like fists and a stinger-like kick, which is used when you score a\n\ +critical hit, and does massive damage, knocking your target far back.", + +"block", TEC_BLOCK, 3, +"pincer fist", TEC_PRECISE, 4 +); + +STYLE("lizard style", 1, "\ +One of the Five Deadly Venoms. Lizard Style focuses on using walls to one's\n\ +advantage. Moving alongside a wall will make you run up along it, giving you\n\ +a large to-hit bonus. Standing by a wall allows you to use it to boost dodge.", + +"block", TEC_BLOCK, 2, +"counter-attack", TEC_COUNTER, 4 +); + +STYLE("toad style", 0, "\ +One of the Five Deadly Venoms. Immensely powerful, and immune to nearly any\n\ +weapon. You may meditate by pausing for a turn; this will give you temporary\n\ +armor, proportional to your Intelligence and Perception.", + +"block", TEC_BLOCK, 3, +"grab", TEC_GRAB, 4 +); + +STYLE("zui quan", 1, "\ +Also known as \"drunken boxing,\" Zui Quan imitates the movement of a drunk\n\ +to confuse the enemy. The turn after you attack, you may dodge any number of\n\ +attacks with no penalty.", + +"stumble and leer at", TEC_FEINT, 3, +"counter-attack", TEC_COUNTER, 4 +); + if (itypes.size() != num_all_items) debugmsg("%d items, %d itypes (+bio)", itypes.size(), num_all_items - 1); diff --git a/iuse.cpp b/iuse.cpp index 22a6721f50..0b109ba334 100644 --- a/iuse.cpp +++ b/iuse.cpp @@ -1009,7 +1009,7 @@ void iuse::two_way_radio(game *g, player *p, item *it, bool t) std::vector in_range; for (int i = 0; i < g->cur_om.npcs.size(); i++) { if (g->cur_om.npcs[i].op_of_u.value >= 4 && - trig_dist(g->levx, g->levy, g->cur_om.npcs[i].mapx, + rl_dist(g->levx, g->levy, g->cur_om.npcs[i].mapx, g->cur_om.npcs[i].mapy) <= 30) in_range.push_back(&(g->cur_om.npcs[i])); } @@ -1046,7 +1046,7 @@ void iuse::radio_on(game *g, player *p, item *it, bool t) std::string message = "Radio: Kssssssssssssh."; for (int k = 0; k < g->cur_om.radios.size(); k++) { int signal = g->cur_om.radios[k].strength - - trig_dist(g->cur_om.radios[k].x, g->cur_om.radios[k].y, + rl_dist(g->cur_om.radios[k].x, g->cur_om.radios[k].y, g->levx, g->levy); if (signal > best_signal) { best_signal = signal; @@ -1351,7 +1351,7 @@ That trap needs a 3x3 space to be clear, centered two tiles from you."); g->add_msg(message.str().c_str()); p->practice(sk_traps, practice); g->m.add_trap(posx, posy, type); - p->moves -= practice * 25; + p->moves -= 100 + practice * 25; if (type == tr_engine) { for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { diff --git a/keypress.cpp b/keypress.cpp index f9cad65e5e..830d046c8c 100644 --- a/keypress.cpp +++ b/keypress.cpp @@ -6,10 +6,10 @@ long input() { long ch = getch(); switch (ch) { - case KEY_UP: return 'k'; - case KEY_LEFT: return 'h'; + case KEY_UP: return 'k'; + case KEY_LEFT: return 'h'; case KEY_RIGHT: return 'l'; - case KEY_DOWN: return 'j'; + case KEY_DOWN: return 'j'; case 459: return '\n'; default: return ch; } @@ -100,6 +100,7 @@ void get_direction(game *g, int &x, int &y, char ch) y = 1; return; case ACTION_PAUSE: + case ACTION_PICKUP: x = 0; y = 0; return; @@ -175,6 +176,7 @@ take_off T\n\ eat E\n\ read R\n\ wield w\n\ +pick_style _\n\ reload r\n\ unload U\n\ throw t\n\ @@ -198,13 +200,14 @@ quit Q\n\ # INFO SCREENS\n\ player_data @\n\ map m :\n\ +missions M\n\ factions #\n\ morale %\n\ help ?\n\ \n\ # DEBUG FUNCTIONS\n\ -debug Z\n\ -debug_scent -\n\ -debug_mon ~\n\ +debug_mode ~\n\ +# debug Z\n\ +# debug_scent -\n\ "; } diff --git a/line.cpp b/line.cpp index 23020327cc..cd11b11f11 100644 --- a/line.cpp +++ b/line.cpp @@ -72,6 +72,14 @@ int rl_dist(int x1, int y1, int x2, int y2) return dy; } +int rl_dist(point a, point b) +{ + int dx = abs(a.x - b.x), dy = abs(a.y - b.y); + if (dx > dy) + return dx; + return dy; +} + double slope_of(std::vector line) { double dX = line.back().x - line.front().x, dY = line.back().y - line.front().y; diff --git a/line.h b/line.h index 5eaa2e45fa..1b24fe4f79 100644 --- a/line.h +++ b/line.h @@ -24,6 +24,7 @@ std::vector line_to(int x1, int y1, int x2, int y2, int t); int trig_dist(int x1, int y1, int x2, int y2); // Roguelike distance; minimum of dX and dY int rl_dist(int x1, int y1, int x2, int y2); +int rl_dist(point a, point b); double slope_of(std::vector line); std::vector continue_line(std::vector line, int distance); direction direction_from(int x1, int y1, int x2, int y2); diff --git a/map.cpp b/map.cpp index 781f820b5b..0a68a423ed 100644 --- a/map.cpp +++ b/map.cpp @@ -55,6 +55,550 @@ map::~map() { } +vehicle& map::veh_at(int x, int y, int &part_num) +{ + if (!inbounds(x, y)) + return nulveh; // Out-of-bounds - null vehicle + int nonant = int(x / SEEX) + int(y / SEEY) * my_MAPSIZE; + + x %= SEEX; + y %= SEEY; + + // must check 3x3 map chunks, as vehicle part may span to neighbour chunk + // we presume that vehicles don't intersect (they shouldn't by any means) + for (int mx = -1; mx <= 1; mx++) + for (int my = -1; my <= 1; my++) + { + int nonant1 = nonant + mx + my * my_MAPSIZE; + if (nonant1 < 0 || nonant1 >= my_MAPSIZE * my_MAPSIZE) + continue; // out of grid + for (int i = 0; i < grid[nonant1].vehicles.size(); i++) + { + vehicle &veh = grid[nonant1].vehicles[i]; + int part = veh.part_at (x - (veh.posx + mx * SEEX), y - (veh.posy + my * SEEY)); + if (part >= 0) + { + part_num = part; + return veh; + } + } + } + return nulveh; +} + +vehicle& map::veh_at(int x, int y) +{ + int part = 0; + vehicle &veh = veh_at (x, y, part); + return veh; +} + +void map::board_vehicle(game *g, int x, int y, player *p) +{ + if (!p) + { + debugmsg ("map::board_vehicle: null player"); + return; + } + int part = 0; + vehicle &veh = veh_at(x, y, part); + if (veh.type == veh_null) + { + debugmsg ("map::board_vehicle: vehicle not found"); + return; + } + int seat_part = veh.part_with_feature (part, vpf_seat); + if (part < 0) + { + debugmsg ("map::board_vehicle: boarding %s (not seat)", veh.part_info(part).name); + return; + } + if (veh.parts[seat_part].passenger) + { + player *psg = veh.get_passenger (seat_part); + debugmsg ("map::board_vehicle: passenger (%s) is already there", psg? psg->name.c_str() : ""); + return; + } + veh.parts[seat_part].passenger = 1; + p->posx = x; + p->posy = y; + p->in_vehicle = true; + if (p == &g->u) + if (x < SEEX * int(my_MAPSIZE / 2) || y < SEEY * int(my_MAPSIZE / 2) || + x >= SEEX * (1 + int(my_MAPSIZE / 2)) || y >= SEEY * (1 + int(my_MAPSIZE / 2))) + g->update_map(x, y); +} + +void map::unboard_vehicle(game *g, int x, int y) +{ + int part = 0; + vehicle &veh = veh_at(x, y, part); + if (veh.type == veh_null) + { + debugmsg ("map::unboard_vehicle: vehicle not found"); + return; + } + int seat_part = veh.part_with_feature (part, vpf_seat, false); + if (part < 0) + { + debugmsg ("map::unboard_vehicle: unboarding %s (not seat)", veh.part_info(part).name); + return; + } + player *psg = veh.get_passenger(seat_part); + if (!psg) + { + debugmsg ("map::unboard_vehicle: passenger not found"); + return; + } + psg->in_vehicle = false; + psg->driving_recoil = 0; + veh.parts[seat_part].passenger = 0; + veh.skidding = true; +} + +void map::destroy_vehicle (vehicle &veh) +{ + int sm = veh.smx + veh.smy * my_MAPSIZE; + int our_i = -1; + for (int i = 0; i < grid[sm].vehicles.size(); i++) + { + if (&grid[sm].vehicles[i] == &veh) + { + our_i = i; + break; + } + } + if (our_i < 0) + { + debugmsg ("destroy_vehicle can't find it! sm=%d", sm); + return; + } + grid[sm].vehicles.erase (grid[sm].vehicles.begin() + our_i); +} + +bool map::displace_vehicle (game *g, int &x, int &y, int dx, int dy, bool test=false) +{ + char ss[256]; + + int x2 = x + dx; + int y2 = y + dy; + int srcx = x; + int srcy = y; + int dstx = x2; + int dsty = y2; + + if (!inbounds(srcx, srcy)) + { + debugmsg ("map::displace_vehicle: coords out of bounds %d,%d->%d,%d", srcx, srcy, dstx, dsty); + return false; + } + + int src_na = int(srcx / SEEX) + int(srcy / SEEY) * my_MAPSIZE; + srcx %= SEEX; + srcy %= SEEY; + + int dst_na = int(dstx / SEEX) + int(dsty / SEEY) * my_MAPSIZE; + dstx %= SEEX; + dsty %= SEEY; + + if (test) + return src_na != dst_na; + + // first, let's find our position in current vehicles vector + int our_i = -1; + for (int i = 0; i < grid[src_na].vehicles.size(); i++) + { + if (grid[src_na].vehicles[i].posx == srcx && + grid[src_na].vehicles[i].posy == srcy) + { + our_i = i; + break; + } + } + if (our_i < 0) + { + debugmsg ("displace_vehicle our_i=%d", our_i); + return false; + } + // move the vehicle + vehicle &veh = grid[src_na].vehicles[our_i]; + // don't let it go off grid + if (!inbounds(x2, y2)) + { + veh.stop(); + } + + // record every passenger inside + std::vector psg_parts = veh.boarded_parts (); + std::vector psgs; + for (int p = 0; p < psg_parts.size(); p++) + psgs.push_back (veh.get_passenger (psg_parts[p])); + + int rec = abs(veh.velocity) / 5 / 100; + + bool need_update = false; + int upd_x, upd_y; + // move passengers + for (int i = 0; i < psg_parts.size(); i++) + { + player *psg = psgs[i]; + int p = psg_parts[i]; + if (!psg) + { + debugmsg ("empty passenger part %d pcoord=%d,%d u=%d,%d?", p, + veh.global_x()+veh.parts[p].precalc_dx[0], + veh.global_y()+veh.parts[p].precalc_dy[0], + g->u.posx, g->u.posy); + continue; + } + int trec = rec - psgs[i]->sklevel[sk_driving]; + if (trec < 0) trec = 0; + // add recoil + psg->driving_recoil = rec; + // displace passenger taking in account vehicle movement (dx, dy) + // and turning: precalc_dx/dy [0] contains previous frame direction, + // and precalc_dx/dy[1] should contain next direction + psg->posx += dx + veh.parts[p].precalc_dx[1] - veh.parts[p].precalc_dx[0]; + psg->posy += dy + veh.parts[p].precalc_dy[1] - veh.parts[p].precalc_dy[0]; + if (psg == &g->u) + { // if passemger is you, we need to update the map + need_update = true; + upd_x = psg->posx; + upd_y = psg->posy; + } + } + for (int p = 0; p < veh.parts.size(); p++) + { + veh.parts[p].precalc_dx[0] = veh.parts[p].precalc_dx[1]; + veh.parts[p].precalc_dy[0] = veh.parts[p].precalc_dy[1]; + } + + veh.posx = dstx; + veh.posy = dsty; + if (src_na != dst_na) + { + vehicle veh1 = veh; + veh1.smx = int(x2 / SEEX); + veh1.smy = int(y2 / SEEY); + grid[dst_na].vehicles.push_back (veh1); + grid[src_na].vehicles.erase (grid[src_na].vehicles.begin() + our_i); + } + + x += dx; + y += dy; + + bool was_update = false; + if (need_update) + if (upd_x < SEEX * int(my_MAPSIZE / 2) || upd_y < SEEY * int(my_MAPSIZE / 2) || + upd_x >= SEEX * (1+int(my_MAPSIZE / 2)) || upd_y >= SEEY * (1+int(my_MAPSIZE / 2))) + { + // map will shift, so adjust vehicle coords we've been passed + if (upd_x < SEEX * int(my_MAPSIZE / 2)) + x += SEEX; + else + if (upd_x >= SEEX * (1+int(my_MAPSIZE / 2))) + x -= SEEX; + if (upd_y < SEEY * int(my_MAPSIZE / 2)) + y += SEEY; + else + if (upd_y >= SEEY * (1+int(my_MAPSIZE / 2))) + y -= SEEY; + g->update_map(upd_x, upd_y); + was_update = true; + } + return (src_na != dst_na) || was_update; +} + +void map::vehmove(game *g) +{ + // give vehicles movement points + for (int i = 0; i < my_MAPSIZE; i++) + for (int j = 0; j < my_MAPSIZE; j++) + { + int sm = i + j * my_MAPSIZE; + for (int v = 0; v < grid[sm].vehicles.size(); v++) + { + vehicle &veh = grid[sm].vehicles[v]; + // velocity is ability to make more one-tile steps per turn + veh.gain_moves (abs (veh.velocity)); + } + } + // move vehicles + bool sm_change; + int count = 0; + do + { + sm_change = false; + for (int i = 0; i < my_MAPSIZE; i++) + { + for (int j = 0; j < my_MAPSIZE; j++) + { + int sm = i + j * my_MAPSIZE; + for (int v = 0; v < grid[sm].vehicles.size(); v++) + { + vehicle &veh = grid[sm].vehicles[v]; + bool pl_ctrl = veh.player_in_control(&g->u); + while (!sm_change && veh.moves > 0 && veh.velocity != 0) + { + int x = veh.posx + i * SEEX; + int y = veh.posy + j * SEEY; + if (has_flag(swimmable, x, y) && + move_cost_ter_only(x, y) == 0) // deep water + { + if (pl_ctrl) + g->add_msg ("Your %s sank.", veh.name.c_str()); + veh.unboard_all (); + // destroy vehicle (sank to nowhere) + grid[sm].vehicles.erase (grid[sm].vehicles.begin() + v); + v--; + break; + } + // one-tile step take some of movement + int mpcost = 500 * move_cost_ter_only(i * SEEX + veh.posx, j * SEEY + veh.posy); + veh.moves -= mpcost; + + if (!veh.valid_wheel_config()) // not enough wheels + { + veh.velocity += veh.velocity < 0? 2000 : -2000; + for (int ep = 0; ep < veh.external_parts.size(); ep++) + { + int p = veh.external_parts[ep]; + int px = x + veh.parts[p].precalc_dx[0]; + int py = y + veh.parts[p].precalc_dy[0]; + ter_id &pter = ter(px, py); + if (pter == t_dirt || pter == t_grass) + pter = t_dirtmound; + } + } + + if (veh.skidding && one_in(4)) // might turn uncontrollably while skidding + veh.move.init (veh.move.dir() + (one_in(2)? -rng(1,3)*15 : rng(1,3)*15)); + else + if (pl_ctrl && rng(0, 4) > g->u.sklevel[sk_driving] && one_in(20)) + { + g->add_msg("You fumble with %s controls.", veh.name.c_str()); + veh.turn (one_in(2)? -15 : 15); + } + if (!veh.boarded_parts().size() && one_in (10)) // eventually send it skidding if no control + veh.skidding = true; + tileray mdir; // the direction we're moving + if (veh.skidding) + { // if skidding, it's the move vector + mdir = veh.move; + } + else + { + if (veh.turn_dir != veh.face.dir()) + mdir.init (veh.turn_dir); // driver turned vehicle, get turn_dir + else + mdir = veh.face; // not turning, keep face.dir + } + mdir.advance (veh.velocity < 0? -1 : 1); + int dx = mdir.dx(); // where do we go + int dy = mdir.dy(); // where do we go + bool can_move = true; + // calculate parts' mount points @ next turn (put them into precalc[1]) + veh.precalc_mounts(1, veh.skidding? veh.turn_dir : mdir.dir()); + + int imp = 0; + // find collisions + for (int ep = 0; ep < veh.external_parts.size(); ep++) + { + int p = veh.external_parts[ep]; + // coords of where part will go due to movement (dx/dy) + // and turning (precalc_dx/dy [1]) + int dsx = x + dx + veh.parts[p].precalc_dx[1]; + int dsy = y + dy + veh.parts[p].precalc_dy[1]; + if (can_move) + imp += veh.part_collision (x, y, p, dsx, dsy); + if (veh.velocity == 0) + can_move = false; + if (!can_move) + break; + } + + int coll_turn = 0; + if (imp > 0) + { +// debugmsg ("collision imp=%d dam=%d-%d", imp, imp/10, imp/6); + if (imp > 100) + veh.damage_all (imp / 20, imp / 10, 1); // shake the vehicle because of collision + std::vector ppl = veh.boarded_parts(); + int vel2 = imp * k_mvel * 100 / (veh.total_mass() / 8); + for (int ps = 0; ps < ppl.size(); ps++) + { + player *psg = veh.get_passenger (ppl[ps]); + if (!psg) + { + debugmsg ("throw passenger: empty passenger at part %d", ppl[ps]); + continue; + } + int throw_roll = rng (vel2/100, vel2/100 * 2); + int psblt = veh.part_with_feature (ppl[ps], vpf_seatbelt); + int sb_bonus = psblt >= 0? veh.part_info(psblt).bonus : 0; + bool throw_it = throw_roll > (psg->str_cur + sb_bonus) * 3; +// debugmsg ("throw vel2=%d roll=%d bonus=%d", vel2, throw_roll, (psg->str_cur + sb_bonus) * 3); + std::string psgname; + if (psg == &g->u) + psgname = "You"; + else + psgname = psg->name; + if (throw_it) + { + if (psgname.length()) + g->add_msg ("%s was hurled from %s's seat by the power of impact!", + psgname.c_str(), veh.name.c_str()); + g->m.unboard_vehicle (g, x+veh.parts[ppl[ps]].precalc_dx[0], y+veh.parts[ppl[ps]].precalc_dy[0]); + g->fling_player_or_monster (psg, 0, + mdir.dir() + rng(0, 60) - 30, + (vel2/100 - sb_bonus < 10? 10 : vel2/100 - sb_bonus)); + } + else + if (veh.part_with_feature (ppl[ps], vpf_controls) >= 0) + { + int lose_ctrl_roll = rng (0, imp); + if (lose_ctrl_roll > psg->dex_cur * 2 + psg->sklevel[sk_driving] * 3) + { + if (psgname.length()) + g->add_msg ("%s lose control of %s.", psgname.c_str(), veh.name.c_str()); + int turn_amount = rng (1, 3) * sqrt (vel2) / 2; + turn_amount /= 15; + if (turn_amount < 1) + turn_amount = 1; + turn_amount *= 15; + if (turn_amount > 120) + turn_amount = 120; + //veh.skidding = true; + //veh.turn (one_in (2)? turn_amount : -turn_amount); + coll_turn = one_in (2)? turn_amount : -turn_amount; + } + } + } + } + // now we're gonna handle traps we're standing on (if we're still moving). + // this is done here before displacement because + // after displacement veh reference would be invdalid. + // damn references! + if (can_move) + for (int ep = 0; ep < veh.external_parts.size(); ep++) + { + int p = veh.external_parts[ep]; + if (veh.part_flag(p, vpf_wheel) && one_in(2)) + if (displace_water (x + veh.parts[p].precalc_dx[0], y + veh.parts[p].precalc_dy[0]) && pl_ctrl) + g->add_msg ("You hear a splash!"); + veh.handle_trap (x + veh.parts[p].precalc_dx[0], y + veh.parts[p].precalc_dy[0], p); + } + + int last_turn_dec = 1; + if (veh.last_turn != 0) + if (veh.last_turn < 0) + { + veh.last_turn += last_turn_dec; + if (veh.last_turn > -last_turn_dec) + veh.last_turn = 0; + } + else + { + veh.last_turn -= last_turn_dec; + if (veh.last_turn < last_turn_dec) + veh.last_turn = 0; + } + int slowdown = veh.skidding? 200 : 20; // mph lost per tile when rolling free + float kslw = (0.1 + veh.k_dynamics()) / ((0.1) + veh.k_mass()); + slowdown = (int) (slowdown * kslw); + if (veh.velocity < 0) + veh.velocity += slowdown; + else + veh.velocity -= slowdown; + if (abs(veh.velocity) < 100) + veh.stop(); + + if (pl_ctrl) + { + // a bit of delay for animation + timespec ts; // Timespec for the animation + ts.tv_sec = 0; + ts.tv_nsec = 50000000; + nanosleep (&ts, 0); + } + + if (can_move) + { + // accept new direction + if (veh.skidding) + veh.face.init (veh.turn_dir); + else + veh.face = mdir; + veh.move = mdir; + if (coll_turn) + { + veh.skidding = true; + veh.turn (coll_turn); + } + // accept new position + // if submap changed, we need to process grid from the beginning. + sm_change = displace_vehicle (g, x, y, dx, dy); + } + else + veh.stop(); + // redraw scene + g->draw(); + if (sm_change) + break; + } // while (veh.moves + if (sm_change) + break; + } //for v + if (sm_change) + break; + } // for j + if (sm_change) + break; + } // for i + count++; +// if (count > 3) +// debugmsg ("vehmove count:%d", count); + if (count > 10) + break; + } while (sm_change); +} + +bool map::displace_water (int x, int y) +{ + if (move_cost_ter_only(x, y) > 0 && has_flag(swimmable, x, y)) // shallow water + { // displace it + int dis_places = 0, sel_place = 0; + for (int pass = 0; pass < 2; pass++) + { // we do 2 passes. + // first, count how many non-water places around + // then choose one within count and fill it with water on second pass + if (pass) + { + sel_place = rng (0, dis_places - 1); + dis_places = 0; + } + for (int tx = -1; tx <= 1; tx++) + for (int ty = -1; ty <= 1; ty++) + { + if ((!tx && !ty) || move_cost_ter_only(x + tx, y + ty) == 0) + continue; + ter_id ter0 = ter (x + tx, y + ty); + if (ter0 == t_water_sh || + ter0 == t_water_dp) + continue; + if (pass && dis_places == sel_place) + { + ter (x + tx, y + ty) = t_water_sh; + ter (x, y) = t_dirt; + return true; + } + dis_places++; + } + } + } + return false; +} + ter_id& map::ter(int x, int y) { if (!INBOUNDS(x, y)) { @@ -94,6 +638,22 @@ std::string map::features(int x, int y) } int map::move_cost(int x, int y) +{ + int vpart = -1; + vehicle &veh = veh_at (x, y, vpart); + if (veh.type != veh_null) + { // moving past vehicle cost + int dpart = veh.part_with_feature(vpart, vpf_obstacle); + if (dpart >= 0 && + (!veh.part_flag(dpart, vpf_openable) || !veh.parts[dpart].open)) + return 0; + else + return 8; + } + return terlist[ter(x, y)].movecost; +} + +int map::move_cost_ter_only(int x, int y) { return terlist[ter(x, y)].movecost; } @@ -103,12 +663,46 @@ bool map::trans(int x, int y) // Control statement is a problem. Normally returning false on an out-of-bounds // is how we stop rays from going on forever. Instead we'll have to include // this check in the ray loop. - return terlist[ter(x, y)].flags & mfb(transparent) && + int vpart = -1; + vehicle &veh = veh_at (x, y, vpart); + bool tertr; + if (veh.type != veh_null) + { + tertr = !veh.part_flag(vpart, vpf_opaque) || veh.parts[vpart].hp <= 0; + if (!tertr) + { + int dpart = veh.part_with_feature(vpart, vpf_openable); + if (dpart >= 0 && veh.parts[dpart].open) + tertr = true; // open opaque door + } + } + else + tertr = terlist[ter(x, y)].flags & mfb(transparent); + return tertr && (field_at(x, y).type == 0 || // Fields may obscure the view, too fieldlist[field_at(x, y).type].transparent[field_at(x, y).density - 1]); } bool map::has_flag(t_flag flag, int x, int y) +{ + if (flag == bashable) + { + int vpart; + vehicle &veh = veh_at (x, y, vpart); + if (veh.type != veh_null && veh.parts[vpart].hp > 0) + { // if there's a vehicle part here... + if (veh.part_with_feature (vpart, vpf_obstacle) >= 0) + { // and it is obstacle... + int p = veh.part_with_feature (vpart, vpf_openable); + if (p < 0 || !veh.parts[p].open) // and not open door + return true; + } + } + } + return terlist[ter(x, y)].flags & mfb(flag); +} + +bool map::has_flag_ter_only(t_flag flag, int x, int y) { return terlist[ter(x, y)].flags & mfb(flag); } @@ -116,12 +710,19 @@ bool map::has_flag(t_flag flag, int x, int y) bool map::is_destructable(int x, int y) { return (has_flag(bashable, x, y) || - (move_cost(x, y) == 0 && !has_flag(transparent, x, y))); + (move_cost(x, y) == 0 && !has_flag(liquid, x, y))); +} + +bool map::is_destructable_ter_only(int x, int y) +{ + return (has_flag_ter_only(bashable, x, y) || + (move_cost(x, y) == 0 && !has_flag(liquid, x, y))); } bool map::is_outside(int x, int y) { - return (ter(x , y ) != t_floor && ter(x - 1, y - 1) != t_floor && + bool out = ( + ter(x , y ) != t_floor && ter(x - 1, y - 1) != t_floor && ter(x - 1, y ) != t_floor && ter(x - 1, y + 1) != t_floor && ter(x , y - 1) != t_floor && ter(x , y ) != t_floor && ter(x , y + 1) != t_floor && ter(x + 1, y - 1) != t_floor && @@ -136,6 +737,13 @@ bool map::is_outside(int x, int y) ter(x + 1, y - 1) != t_floor_wax && ter(x + 1, y ) != t_floor_wax && ter(x + 1, y + 1) != t_floor_wax ); + if (out) { + int vpart; + vehicle &veh = veh_at (x, y, vpart); + if (veh.type != veh_null && veh.is_inside(vpart)) + out = false; + } + return out; } bool map::flammable_items_at(int x, int y) @@ -166,7 +774,7 @@ point map::random_outdoor_tile() return options[rng(0, options.size() - 1)]; } -bool map::bash(int x, int y, int str, std::string &sound) +bool map::bash(int x, int y, int str, std::string &sound, int *res) { sound = ""; bool smashed_web = false; @@ -188,9 +796,22 @@ bool map::bash(int x, int y, int str, std::string &sound) } } + int result = -1; + int vpart; + vehicle &veh = veh_at(x, y, vpart); + if (veh.type != veh_null) + { + veh.damage (vpart, str, 1); + result = str; + sound += "crash!"; + return true; + } + switch (ter(x, y)) { case t_wall_wood: - if (str >= rng(0, 120) && str >= rng(0, 120)) { + result = rng(0, 120); + if (res) *res = result; + if (str >= result && str >= rng(0, 120)) { sound += "crunch!"; ter(x, y) = t_wall_wood_chipped; int num_boards = rng(0, 2); @@ -204,7 +825,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_wall_wood_chipped: - if (str >= rng(0, 100) && str >= rng(0, 100)) { + result = rng(0, 100); + if (res) *res = result; + if (str >= result && str >= rng(0, 100)) { sound += "crunch!"; ter(x, y) = t_wall_wood_broken; int num_boards = rng(3, 8); @@ -218,7 +841,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_wall_wood_broken: - if (str >= rng(0, 80) && str >= rng(0, 80)) { + result = rng(0, 80); + if (res) *res = result; + if (str >= result && str >= rng(0, 80)) { sound += "crash!"; ter(x, y) = t_dirt; int num_boards = rng(4, 10); @@ -234,7 +859,9 @@ bool map::bash(int x, int y, int str, std::string &sound) case t_door_c: case t_door_locked: case t_door_locked_alarm: - if (str >= rng(0, 40)) { + result = rng(0, 40); + if (res) *res = result; + if (str >= result) { sound += "smash!"; ter(x, y) = t_door_b; return true; @@ -245,7 +872,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_door_b: - if (str >= rng(0, 30)) { + result = rng(0, 30); + if (res) *res = result; + if (str >= result) { sound += "crash!"; ter(x, y) = t_door_frame; int num_boards = rng(2, 6); @@ -260,7 +889,9 @@ bool map::bash(int x, int y, int str, std::string &sound) case t_window: case t_window_alarm: - if (str >= rng(0, 6)) { + result = rng(0, 6); + if (res) *res = result; + if (str >= result) { sound += "glass breaking!"; ter(x, y) = t_window_frame; return true; @@ -271,7 +902,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_door_boarded: - if (str >= dice(3, 50)) { + result = dice(3, 50); + if (res) *res = result; + if (str >= result) { sound += "crash!"; ter(x, y) = t_door_frame; int num_boards = rng(0, 2); @@ -285,7 +918,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_window_boarded: - if (str >= dice(3, 30)) { + result = dice(3, 30); + if (res) *res = result; + if (str >= result) { sound += "crash!"; ter(x, y) = t_window_frame; int num_boards = rng(0, 2) * rng(0, 1); @@ -299,7 +934,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_paper: - if (str >= dice(2, 6) - 2) { + result = dice(2, 6) - 2; + if (res) *res = result; + if (str >= result) { sound += "rrrrip!"; ter(x, y) = t_dirt; return true; @@ -310,7 +947,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_toilet: - if (str >= dice(8, 4) - 8) { + result = dice(8, 4) - 8; + if (res) *res = result; + if (str >= result) { sound += "porcelain breaking!"; ter(x, y) = t_rubble; return true; @@ -322,7 +961,9 @@ bool map::bash(int x, int y, int str, std::string &sound) case t_dresser: case t_bookcase: - if (str >= dice(3, 45)) { + result = dice(3, 45); + if (res) *res = result; + if (str >= result) { sound += "smash!"; ter(x, y) = t_floor; int num_boards = rng(4, 12); @@ -340,7 +981,9 @@ bool map::bash(int x, int y, int str, std::string &sound) case t_wall_glass_h_alarm: case t_wall_glass_v_alarm: case t_door_glass_c: - if (str >= rng(0, 20)) { + result = rng(0, 20); + if (res) *res = result; + if (str >= result) { sound += "glass breaking!"; ter(x, y) = t_floor; return true; @@ -352,7 +995,9 @@ bool map::bash(int x, int y, int str, std::string &sound) case t_reinforced_glass_h: case t_reinforced_glass_v: - if (str >= rng(60, 100)) { + result = rng(60, 100); + if (res) *res = result; + if (str >= result) { sound += "glass breaking!"; ter(x, y) = t_floor; return true; @@ -363,7 +1008,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_tree_young: - if (str >= rng(0, 50)) { + result = rng(0, 50); + if (res) *res = result; + if (str >= result) { sound += "crunch!"; ter(x, y) = t_underbrush; int num_sticks = rng(0, 3); @@ -377,7 +1024,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_underbrush: - if (str >= rng(0, 30) && !one_in(4)) { + result = rng(0, 30); + if (res) *res = result; + if (str >= result && !one_in(4)) { sound += "crunch."; ter(x, y) = t_dirt; return true; @@ -399,7 +1048,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_marloss: - if (str > rng(0, 40)) { + result = rng(0, 40); + if (res) *res = result; + if (str >= result) { sound += "crunch!"; ter(x, y) = t_fungus; return true; @@ -410,7 +1061,9 @@ bool map::bash(int x, int y, int str, std::string &sound) break; case t_vat: - if (str >= dice(2, 20)) { + result = dice(2, 20); + if (res) *res = result; + if (str >= result) { sound += "ker-rash!"; ter(x, y) = t_floor; return true; @@ -420,7 +1073,9 @@ bool map::bash(int x, int y, int str, std::string &sound) } case t_crate_c: case t_crate_o: - if (str >= dice(4, 20)) { + result = dice(4, 20); + if (res) *res = result; + if (str >= result) { sound += "smash"; ter(x, y) = t_dirt; int num_boards = rng(1, 5); @@ -431,8 +1086,8 @@ bool map::bash(int x, int y, int str, std::string &sound) sound += "wham!"; return true; } - } + if (res) *res = result; if (move_cost(x, y) == 0) { sound += "thump!"; return true; @@ -444,10 +1099,11 @@ bool map::bash(int x, int y, int str, std::string &sound) void map::destroy(game *g, int x, int y, bool makesound) { switch (ter(x, y)) { + case t_gas_pump: - if (one_in(4)) + if (one_in(3)) g->explosion(x, y, 40, 0, true); - else + else { for (int i = x - 2; i <= x + 2; i++) { for (int j = y - 2; j <= y + 2; j++) { if (move_cost(i, j) > 0 && one_in(3)) @@ -456,6 +1112,7 @@ void map::destroy(game *g, int x, int y, bool makesound) add_item(i, j, g->itypes[itm_steel_chunk], 0); } } + } ter(x, y) = t_rubble; break; @@ -486,7 +1143,7 @@ void map::destroy(game *g, int x, int y, bool makesound) break; default: - if (has_flag(explodes, x, y)) + if (has_flag(explodes, x, y) && one_in(2)) g->explosion(x, y, 40, 0, true); ter(x, y) = t_rubble; } @@ -504,6 +1161,14 @@ void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) g->add_event(EVENT_WANTED, int(g->turn) + 300, 0, g->levx, g->levy); } + int vpart; + vehicle &veh = veh_at (x, y, vpart); + if (veh.type != veh_null) + { + bool inc = (flags & mfb(IF_AMMO_INCENDIARY) || flags & mfb(IF_AMMO_FLAME)); + dam = veh.damage (vpart, dam, inc? 2 : 0, hit_items); + } + switch (ter(x, y)) { case t_wall_wood_broken: @@ -1019,6 +1684,11 @@ trap_id& map::tr_at(int x, int y) nultrap = tr_null; return nultrap; // Out-of-bounds, return our null trap } + + if (terlist[ grid[nonant].ter[x][y] ].trap != tr_null) { + nultrap = terlist[ grid[nonant].ter[x][y] ].trap; + return nultrap; + } return grid[nonant].trp[x][y]; } @@ -1054,13 +1724,22 @@ void map::disarm_trap(game *g, int x, int y) add_item(x, y, g->itypes[comp[i]], 0); } tr_at(x, y) = tr_null; - } else if (roll >= diff * .8) + if(diff > 1.25*g->u.sklevel[sk_traps]) // failure might have set off trap + g->u.practice(sk_traps, 1.5*(diff - g->u.sklevel[sk_traps])); + } else if (roll >= diff * .8) { g->add_msg("You fail to disarm the trap."); + if(diff > 1.25*g->u.sklevel[sk_traps]) + g->u.practice(sk_traps, 1.5*(diff - g->u.sklevel[sk_traps])); + } else { g->add_msg("You fail to disarm the trap, and you set it off!"); trap* tr = g->traps[tr_at(x, y)]; trapfunc f; (f.*(tr->act))(g, x, y); + if(diff - roll <= 6) + // Give xp for failing, but not if we failed terribly (in which + // case the trap may not be disarmable). + g->u.practice(sk_traps, 2*diff); } } @@ -1101,8 +1780,8 @@ bool map::add_field(game *g, int x, int y, field_id t, unsigned char density) grid[nonant].fld[x][y] = field(t, density, 0); if (g != NULL && x == g->u.posx && y == g->u.posy && grid[nonant].fld[x][y].is_dangerous()) { - g->cancel_activity(); - g->add_msg("You're in a %s!", fieldlist[t].name[density - 1].c_str()); + g->cancel_activity_query("You're in a %s!", + fieldlist[t].name[density - 1].c_str()); } return true; } @@ -1180,15 +1859,19 @@ void map::drawsq(WINDOW* w, player &u, int x, int y, bool invert, int k = x + SEEX - u.posx; int j = y + SEEY - u.posy; nc_color tercol; - char sym = terlist[ter(x, y)].sym; + long sym = terlist[ter(x, y)].sym; bool hi = false; + bool normal_tercol = false; // indicates that tile color is not changed by effects (boomered, nigh vision) if (u.has_disease(DI_BOOMERED)) tercol = c_magenta; else if ((u.is_wearing(itm_goggles_nv) && u.has_active_item(itm_UPS_on)) || u.has_active_bionic(bio_night_vision)) tercol = c_ltgreen; else + { + normal_tercol = true; tercol = terlist[ter(x, y)].color; + } if (move_cost(x, y) == 0 && has_flag(swimmable, x, y) && !u.underwater) show_items = false; // Can only see underwater items if WE are underwater // If there's a trap here, and we have sufficient perception, draw that instead @@ -1231,6 +1914,16 @@ void map::drawsq(WINDOW* w, player &u, int x, int y, bool invert, sym = i_at(x, y)[i_at(x, y).size() - 1].symbol(); } } + + int veh_part = 0; + vehicle &veh = veh_at(x, y, veh_part); + if (veh.type != veh_null) + { + sym = special_symbol (veh.face.dir_symbol(veh.part_sym(veh_part))); + if (normal_tercol) + tercol = veh.part_color(veh_part); + } + if (invert) mvwputch_inv(w, j, k, tercol, sym); else if (hi) @@ -1302,6 +1995,7 @@ bool map::sees(int Fx, int Fy, int Tx, int Ty, int range, int &tc) } return false; } + return false; // Shouldn't ever be reached, but there it is. } bool map::clear_path(int Fx, int Fy, int Tx, int Ty, int range, int cost_min, @@ -1366,6 +2060,7 @@ bool map::clear_path(int Fx, int Fy, int Tx, int Ty, int range, int cost_min, } return false; } + return false; // Shouldn't ever be reached, but there it is. } // Bash defaults to true. @@ -1531,10 +2226,17 @@ void map::shift(game *g, int wx, int wy, int sx, int sy) } return; } + +// if player is driving vehicle, (s)he must be shifted with vehicle too + if (g->u.in_vehicle && (sx !=0 || sy != 0)) + { + g->u.posx -= sx * SEEX; + g->u.posy -= sy * SEEY; + } + // Shift the map sx submaps to the right and sy submaps down. // sx and sy should never be bigger than +/-1. // wx and wy are our position in the world, for saving/loading purposes. - if (sx >= 0) { for (int gridx = 0; gridx < my_MAPSIZE; gridx++) { if (sy >= 0) { @@ -1667,6 +2369,11 @@ void map::saven(overmap *om, unsigned int turn, int worldx, int worldy, tmpsp.mission_id << (tmpsp.friendly ? " 1 " : " 0 ") << tmpsp.name << std::endl; } +// Output the vehicles + for (int i = 0; i < grid[n].vehicles.size(); i++) { + fout << "V "; + grid[n].vehicles[i].save (fout); + } // Output the computer if (grid[n].comp.name != "") fout << "c " << grid[n].comp.save_data() << std::endl; @@ -1695,6 +2402,7 @@ bool map::loadn(game *g, int worldx, int worldy, int gridx, int gridy) grid[gridn].active_item_count = 0; grid[gridn].field_count = 0; grid[gridn].comp = computer(); + grid[gridn].vehicles.clear(); sprintf(fname, "save/m.%d.%d.%d", g->cur_om.posx * OMAPX * 2 + worldx + gridx, g->cur_om.posy * OMAPY * 2 + worldy + gridy, @@ -1728,7 +2436,7 @@ bool map::loadn(game *g, int worldx, int worldy, int gridx, int gridy) grid[gridn].rad[i][j] = radtmp; } } -// Load items and traps and fields and spawn points +// Load items and traps and fields and spawn points and vehicles while (!mapin.eof()) { t = 0; mapin >> ch; @@ -1764,6 +2472,12 @@ bool map::loadn(game *g, int worldx, int worldy, int gridx, int gridy) spawn_point tmp(mon_id(t), a, itx, ity, tmpfac, tmpmis, (tmpfriend == '1'), spawnname); grid[gridn].spawns.push_back(tmp); + } else if (!mapin.eof() && ch == 'V') { + vehicle veh(g); + veh.load (mapin); + veh.smx = gridx; + veh.smy = gridy; + grid[gridn].vehicles.push_back(veh); } else if (!mapin.eof() && ch == 'c') { getline(mapin, databuff); grid[gridn].comp.load_data(databuff); @@ -1821,6 +2535,15 @@ void map::copy_grid(int to, int from) grid[to].spawns.clear(); for (int i = 0; i < grid[from].spawns.size(); i++) grid[to].spawns.push_back(grid[from].spawns[i]); + + grid[to].vehicles.clear (); + for (int i = 0; i < grid[from].vehicles.size(); i++) + { + grid[to].vehicles.push_back(grid[from].vehicles[i]); + int ind = grid[to].vehicles.size() - 1; + grid[to].vehicles[ind].smx = to % my_MAPSIZE; + grid[to].vehicles[ind].smy = to / my_MAPSIZE; + } } void map::spawn_monsters(game *g) @@ -1935,3 +2658,4 @@ void map::cast_to_nonant(int &x, int &y, int &n) } */ + diff --git a/map.h b/map.h index 24cdc84b45..d166916ecd 100644 --- a/map.h +++ b/map.h @@ -17,6 +17,7 @@ #include "item.h" #include "monster.h" #include "npc.h" +#include "vehicle.h" #define MAPSIZE 11 @@ -47,7 +48,8 @@ class map void clear_traps(); // Movement and LOS - int move_cost(int x, int y); // Cost to move through; 0 = impassible + int move_cost(int x, int y); // Cost to move through; 0 = impassible + int move_cost_ter_only(int x, int y); // same as above, but don't take vehicles into account bool trans(int x, int y); // Transparent? // (Fx, Fy) sees (Tx, Ty), within a range of (range)? // tc indicates the Bresenham line used to connect the two points, and may @@ -59,12 +61,29 @@ class map // route() generates an A* best path; if bash is true, we can bash through doors std::vector route(int Fx, int Fy, int Tx, int Ty, bool bash = true); +// vehicles + vehicle& veh_at(int x, int y, int &part_num); // checks, if tile is occupied by vehicle and by which part + vehicle& veh_at(int x, int y); // checks, if tile is occupied by vehicle + void board_vehicle(game *g, int x, int y, player *p); // put player on vehicle at x,y + void unboard_vehicle(game *g, int x, int y); // remoev player from vehicle at x,y + + void destroy_vehicle (vehicle &veh); +// Change vehicle coords and move vehicle's driver along. Returns true, if there was a submap change. +// If test is true, function only checks for submap change, no displacement +// WARNING: not checking collisions! + bool displace_vehicle (game *g, int &x, int &y, int dx, int dy, bool test); + void vehmove(game* g); // Vehicle movement +// move water under wheels. true if moved + bool displace_water (int x, int y); + // Terrain ter_id& ter(int x, int y); // Terrain at coord (x, y); {x|y}=(0, SEE{X|Y}*3] std::string tername(int x, int y); // Name of terrain at (x, y) std::string features(int x, int y); // Words relevant to terrain (sharp, etc) - bool has_flag(t_flag flag, int x, int y); - bool is_destructable(int x, int y); + bool has_flag(t_flag flag, int x, int y); // checks terrain and vehicles + bool has_flag_ter_only(t_flag flag, int x, int y); // only checks terrain + bool is_destructable(int x, int y); // checks terrain and vehicles + bool is_destructable_ter_only(int x, int y); // only checks terrain bool is_outside(int x, int y); bool flammable_items_at(int x, int y); point random_outdoor_tile(); @@ -72,7 +91,8 @@ class map void translate(ter_id from, ter_id to); // Change all instances of $from->$to bool close_door(int x, int y); bool open_door(int x, int y, bool inside); - bool bash(int x, int y, int str, std::string &sound); + // bash: if res pointer is supplied, res will contain absorbed impact or -1 + bool bash(int x, int y, int str, std::string &sound, int *res = 0); void destroy(game *g, int x, int y, bool makesound); void shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags); bool hit_with_acid(game *g, int x, int y); @@ -91,6 +111,7 @@ class map void add_item(int x, int y, item new_item); void process_active_items(game *g); void process_active_items_in_submap(game *g, int nonant); + void process_vehicles(game *g); void use_amount(point origin, int range, itype_id type, int quantity, bool use_container = false); @@ -122,6 +143,7 @@ class map int faction_id = -1, int mission_id = -1, std::string name = "NONE"); void add_spawn(monster *mon); + vehicle *add_vehicle(game *g, vhtype_id type, int x, int y, int dir); computer* add_computer(int x, int y, std::string name, int security); std::vector *itypes; @@ -145,6 +167,7 @@ class map ter_id nulter; // Returned when &ter() is asked for an OOB value trap_id nultrap; // Returned when &tr_at() is asked for an OOB value field nulfield; // Returned when &field_at() is asked for an OOB value + vehicle nulveh; // Returned when &veh_at() is asked for an OOB value int nulrad; // OOB &radiation() std::vector *traps; diff --git a/mapdata.h b/mapdata.h index 03965a05c7..755105b635 100644 --- a/mapdata.h +++ b/mapdata.h @@ -9,12 +9,11 @@ #include "monster.h" #include "enums.h" #include "computer.h" +#include "vehicle.h" class game; class monster; -#define MAP_EXTRA_CHANCE 40 - #ifndef SEEX // SEEX is how far the player can see in the X direction (at #define SEEX 12 // least, without scrolling). All map segments will need to be #endif // at least this wide. The map therefore needs to be 3x as wide. @@ -38,6 +37,7 @@ enum t_flag { l_flammable, // Harder to light on fire, but still possible explodes, // Explodes when on fire diggable, // Digging monsters, seeding monsters, digging w/ shovel, etc. + liquid, // Blocks movement but isn't a wall, e.g. lava or water swimmable, // You (and monsters) swim here sharp, // May do minor damage to players/monsters passing it rough, // May hurt the player's feet @@ -48,6 +48,7 @@ enum t_flag { console, // Used as a computer alarmed, // Sets off an alarm if smashed supports_roof,// Used as a boundary for roof construction + thin_obstacle,// passable by player and monsters, but not by vehicles num_t_flags // MUST be last }; @@ -56,6 +57,7 @@ struct ter_t { char sym; nc_color color; unsigned char movecost; + trap_id trap; unsigned long flags;// : num_t_flags; }; @@ -90,7 +92,7 @@ t_window, t_window_alarm, t_window_empty, t_window_frame, t_window_boarded, t_rock, t_fault, t_paper, // Tree -t_tree, t_tree_young, t_underbrush, t_shrub, +t_tree, t_tree_young, t_underbrush, t_shrub, t_log, t_root_wall, t_wax, t_floor_wax, t_fence_v, t_fence_h, @@ -132,279 +134,281 @@ num_terrain_types }; const ter_t terlist[num_terrain_types] = { // MUST match enum ter_id above! -{"nothing", ' ', c_white, 2, +{"nothing", ' ', c_white, 2, tr_null, mfb(transparent)|mfb(diggable)}, -{"empty space", '#', c_black, 2, +{"empty space", '#', c_black, 2, tr_ledge, mfb(transparent)}, -{"dirt", '.', c_brown, 2, +{"dirt", '.', c_brown, 2, tr_null, mfb(transparent)|mfb(diggable)}, -{"mound of dirt", '#', c_brown, 3, +{"mound of dirt", '#', c_brown, 3, tr_null, mfb(transparent)|mfb(diggable)}, -{"shallow pit", '0', c_yellow, 8, +{"shallow pit", '0', c_yellow, 8, tr_null, mfb(transparent)|mfb(diggable)}, -{"pit", '0', c_brown, 10, +{"pit", '0', c_brown, 10, tr_pit, mfb(transparent)|mfb(diggable)}, -{"spiked pit", '0', c_ltred, 10, +{"spiked pit", '0', c_ltred, 10, tr_spike_pit, mfb(transparent)|mfb(diggable)}, -{"rock floor", '.', c_ltgray, 2, +{"rock floor", '.', c_ltgray, 2, tr_null, mfb(transparent)}, -{"pile of rubble", '#', c_ltgray, 4, +{"pile of rubble", '#', c_ltgray, 4, tr_null, mfb(transparent)|mfb(rough)|mfb(diggable)}, -{"metal wreckage", '#', c_cyan, 5, +{"metal wreckage", '#', c_cyan, 5, tr_null, mfb(transparent)|mfb(rough)|mfb(sharp)|mfb(container)}, -{"grass", '.', c_green, 2, +{"grass", '.', c_green, 2, tr_null, mfb(transparent)|mfb(diggable)}, -{"metal floor", '.', c_ltcyan, 2, +{"metal floor", '.', c_ltcyan, 2, tr_null, mfb(transparent)}, -{"pavement", '.', c_dkgray, 2, +{"pavement", '.', c_dkgray, 2, tr_null, mfb(transparent)}, -{"yellow pavement", '.', c_yellow, 2, +{"yellow pavement", '.', c_yellow, 2, tr_null, mfb(transparent)}, -{"sidewalk", '.', c_ltgray, 2, +{"sidewalk", '.', c_ltgray, 2, tr_null, mfb(transparent)}, -{"floor", '.', c_cyan, 2, +{"floor", '.', c_cyan, 2, tr_null, mfb(transparent)|mfb(l_flammable)}, -{"metal grate", '#', c_dkgray, 2, +{"metal grate", '#', c_dkgray, 2, tr_null, mfb(transparent)}, -{"slime", '~', c_green, 6, +{"slime", '~', c_green, 6, tr_null, mfb(transparent)|mfb(container)|mfb(flammable)}, -{"walkway", '#', c_yellow, 2, +{"walkway", '#', c_yellow, 2, tr_null, mfb(transparent)}, -{"half-built wall", '#', c_ltred, 4, +{"half-built wall", '#', c_ltred, 4, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)}, -{"wooden wall", '#', c_ltred, 0, +{"wooden wall", '#', c_ltred, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"chipped wood wall",'#', c_ltred, 0, +{"chipped wood wall",'#', c_ltred, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"broken wood wall", '&', c_ltred, 0, +{"broken wood wall", '&', c_ltred, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)| mfb(supports_roof)}, -{"wall", '|', c_ltgray, 0, +{"wall", '|', c_ltgray, 0, tr_null, mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"wall", '-', c_ltgray, 0, +{"wall", '-', c_ltgray, 0, tr_null, mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"metal wall", '|', c_cyan, 0, +{"metal wall", '|', c_cyan, 0, tr_null, mfb(noitem)|mfb(noitem)|mfb(supports_roof)}, -{"metal wall", '-', c_cyan, 0, +{"metal wall", '-', c_cyan, 0, tr_null, mfb(noitem)|mfb(noitem)|mfb(supports_roof)}, -{"glass wall", '|', c_ltcyan, 0, +{"glass wall", '|', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(noitem)|mfb(supports_roof)}, -{"glass wall", '-', c_ltcyan, 0, +{"glass wall", '-', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(noitem)|mfb(supports_roof)}, -{"glass wall", '|', c_ltcyan, 0, // Alarmed +{"glass wall", '|', c_ltcyan, 0, tr_null, // Alarmed mfb(transparent)|mfb(bashable)|mfb(alarmed)|mfb(noitem)| mfb(supports_roof)}, -{"glass wall", '-', c_ltcyan, 0, // Alarmed +{"glass wall", '-', c_ltcyan, 0, tr_null, // Alarmed mfb(transparent)|mfb(bashable)|mfb(alarmed)|mfb(noitem)| mfb(supports_roof)}, -{"reinforced glass", '|', c_ltcyan, 0, +{"reinforced glass", '|', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(noitem)|mfb(supports_roof)}, -{"reinforced glass", '-', c_ltcyan, 0, +{"reinforced glass", '-', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(noitem)|mfb(supports_roof)}, -{"metal bars", '"', c_ltgray, 0, +{"metal bars", '"', c_ltgray, 0, tr_null, mfb(transparent)|mfb(noitem)}, -{"closed wood door", '+', c_brown, 0, +{"closed wood door", '+', c_brown, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(door)|mfb(noitem)|mfb(supports_roof)}, -{"damaged wood door",'&', c_brown, 0, +{"damaged wood door",'&', c_brown, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)| mfb(supports_roof)}, -{"open wood door", '\'', c_brown, 2, +{"open wood door", '\'', c_brown, 2, tr_null, mfb(flammable)|mfb(transparent)|mfb(supports_roof)}, -{"closed wood door", '+', c_brown, 0, // Actually locked +{"closed wood door", '+', c_brown, 0, tr_null, // Actually locked mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"closed wood door", '+', c_brown, 0, // Locked and alarmed +{"closed wood door", '+', c_brown, 0, tr_null, // Locked and alarmed mfb(bashable)|mfb(flammable)|mfb(alarmed)|mfb(noitem)| mfb(supports_roof)}, -{"empty door frame", '.', c_brown, 2, +{"empty door frame", '.', c_brown, 2, tr_null, mfb(flammable)|mfb(transparent)|mfb(supports_roof)}, -{"boarded up door", '#', c_brown, 0, +{"boarded up door", '#', c_brown, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"closed metal door",'+', c_cyan, 0, +{"closed metal door",'+', c_cyan, 0, tr_null, mfb(noitem)|mfb(supports_roof)}, -{"open metal door", '\'', c_cyan, 2, +{"open metal door", '\'', c_cyan, 2, tr_null, mfb(transparent)|mfb(supports_roof)}, -{"closed metal door",'+', c_cyan, 0, // Actually locked +{"closed metal door",'+', c_cyan, 0, tr_null, // Actually locked mfb(noitem)|mfb(supports_roof)}, -{"closed glass door",'+', c_ltcyan, 0, +{"closed glass door",'+', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(door)|mfb(noitem)|mfb(supports_roof)}, -{"open glass door", '\'', c_ltcyan, 2, +{"open glass door", '\'', c_ltcyan, 2, tr_null, mfb(transparent)|mfb(supports_roof)}, -{"bulletin board", '6', c_blue, 0, +{"bulletin board", '6', c_blue, 0, tr_null, mfb(flammable)|mfb(noitem)}, -{"makeshift portcullis", '&', c_cyan, 0, +{"makeshift portcullis", '&', c_cyan, 0, tr_null, mfb(noitem)}, -{"window", '"', c_ltcyan, 0, +{"window", '"', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)| mfb(supports_roof)}, -{"window", '"', c_ltcyan, 0, // Actually alarmed +{"window", '"', c_ltcyan, 0, tr_null, // Actually alarmed mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(alarmed)|mfb(noitem)| mfb(supports_roof)}, -{"empty window", '0', c_yellow, 8, +{"empty window", '0', c_yellow, 8, tr_null, mfb(transparent)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"window frame", '0', c_ltcyan, 8, +{"window frame", '0', c_ltcyan, 8, tr_null, mfb(transparent)|mfb(sharp)|mfb(flammable)|mfb(noitem)| mfb(supports_roof)}, -{"boarded up window",'#', c_brown, 0, +{"boarded up window",'#', c_brown, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"solid rock", '#', c_white, 0, +{"solid rock", '#', c_white, 0, tr_null, mfb(noitem)|mfb(supports_roof)}, -{"odd fault", '#', c_magenta, 0, +{"odd fault", '#', c_magenta, 0, tr_null, mfb(noitem)|mfb(supports_roof)}, -{"paper wall", '#', c_white, 0, +{"paper wall", '#', c_white, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(noitem)}, -{"tree", '7', c_green, 0, +{"tree", '7', c_green, 0, tr_null, mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"young tree", '1', c_green, 0, +{"young tree", '1', c_green, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)}, -{"underbrush", '#', c_ltgreen, 6, +{"underbrush", '#', c_ltgreen, 6, tr_null, mfb(transparent)|mfb(bashable)|mfb(diggable)|mfb(container)|mfb(rough)| mfb(flammable)}, -{"shrub", '#', c_green, 0, +{"shrub", '#', c_green, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(container)|mfb(flammable)}, -{"root wall", '#', c_brown, 0, +{"log", '1', c_brown, 4, tr_null, + mfb(transparent)|mfb(flammable)|mfb(diggable)}, +{"root wall", '#', c_brown, 0, tr_null, mfb(noitem)|mfb(supports_roof)}, -{"wax wall", '#', c_yellow, 0, +{"wax wall", '#', c_yellow, 0, tr_null, mfb(container)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"wax floor", '.', c_yellow, 2, +{"wax floor", '.', c_yellow, 2, tr_null, mfb(transparent)|mfb(l_flammable)}, -{"picket fence", '|', c_brown, 3, - mfb(transparent)|mfb(diggable)|mfb(flammable)|mfb(noitem)}, -{"picket fence", '-', c_brown, 3, - mfb(transparent)|mfb(diggable)|mfb(flammable)|mfb(noitem)}, -{"railing", '|', c_yellow, 3, - mfb(transparent)|mfb(noitem)}, -{"railing", '-', c_yellow, 3, - mfb(transparent)|mfb(noitem)}, -{"marloss bush", '1', c_dkgray, 0, +{"picket fence", '|', c_brown, 3, tr_null, + mfb(transparent)|mfb(diggable)|mfb(flammable)|mfb(noitem)|mfb(thin_obstacle)}, +{"picket fence", '-', c_brown, 3, tr_null, + mfb(transparent)|mfb(diggable)|mfb(flammable)|mfb(noitem)|mfb(thin_obstacle)}, +{"railing", '|', c_yellow, 3, tr_null, + mfb(transparent)|mfb(noitem)|mfb(thin_obstacle)}, +{"railing", '-', c_yellow, 3, tr_null, + mfb(transparent)|mfb(noitem)|mfb(thin_obstacle)}, +{"marloss bush", '1', c_dkgray, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)}, -{"fungal bed", '#', c_dkgray, 3, +{"fungal bed", '#', c_dkgray, 3, tr_null, mfb(transparent)|mfb(l_flammable)|mfb(diggable)}, -{"fungal tree", '7', c_dkgray, 0, +{"fungal tree", '7', c_dkgray, 0, tr_null, mfb(flammable)|mfb(noitem)}, -{"shallow water", '~', c_ltblue, 5, +{"shallow water", '~', c_ltblue, 5, tr_null, + mfb(transparent)|mfb(liquid)|mfb(swimmable)}, +{"deep water", '~', c_blue, 0, tr_null, + mfb(transparent)|mfb(liquid)|mfb(swimmable)}, +{"sewage", '~', c_ltgreen, 6, tr_null, mfb(transparent)|mfb(swimmable)}, -{"deep water", '~', c_blue, 0, - mfb(transparent)|mfb(swimmable)}, -{"sewage", '~', c_ltgreen, 6, - mfb(transparent)|mfb(swimmable)}, -{"lava", '~', c_red, 0, - mfb(transparent)}, -{"bed", '#', c_magenta, 5, +{"lava", '~', c_red, 4, tr_lava, + mfb(transparent)|mfb(liquid)}, +{"bed", '#', c_magenta, 5, tr_null, mfb(transparent)|mfb(container)|mfb(flammable)}, -{"toilet", '&', c_white, 0, +{"toilet", '&', c_white, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(l_flammable)}, -{"sandbox", '#', c_yellow, 3, +{"sandbox", '#', c_yellow, 3, tr_null, mfb(transparent)}, -{"slide", '#', c_ltcyan, 4, +{"slide", '#', c_ltcyan, 4, tr_null, mfb(transparent)}, -{"monkey bars", '#', c_cyan, 4, +{"monkey bars", '#', c_cyan, 4, tr_null, mfb(transparent)}, -{"backboard", '7', c_red, 0, +{"backboard", '7', c_red, 0, tr_null, mfb(transparent)}, -{"bench", '#', c_brown, 3, +{"bench", '#', c_brown, 3, tr_null, mfb(transparent)|mfb(flammable)}, -{"table", '#', c_red, 4, +{"table", '#', c_red, 4, tr_null, mfb(transparent)|mfb(flammable)}, -{"pool table", '#', c_green, 4, +{"pool table", '#', c_green, 4, tr_null, mfb(transparent)|mfb(flammable)}, -{"gasoline pump", '&', c_red, 0, +{"gasoline pump", '&', c_red, 0, tr_null, mfb(transparent)|mfb(explodes)|mfb(noitem)}, -{"smashed gas pump", '&', c_ltred, 0, +{"smashed gas pump", '&', c_ltred, 0, tr_null, mfb(transparent)|mfb(noitem)}, -{"missile", '#', c_ltblue, 0, +{"missile", '#', c_ltblue, 0, tr_null, mfb(explodes)|mfb(noitem)}, -{"blown-out missile",'#', c_ltgray, 0, +{"blown-out missile",'#', c_ltgray, 0, tr_null, mfb(noitem)}, -{"counter", '#', c_blue, 4, +{"counter", '#', c_blue, 4, tr_null, mfb(transparent)|mfb(flammable)}, -{"radio tower", '&', c_ltgray, 0, +{"radio tower", '&', c_ltgray, 0, tr_null, mfb(noitem)}, -{"radio controls", '6', c_green, 0, +{"radio controls", '6', c_green, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(noitem)}, -{"broken console", '6', c_ltgray, 0, +{"broken console", '6', c_ltgray, 0, tr_null, mfb(transparent)|mfb(noitem)}, -{"computer console", '6', c_blue, 0, +{"computer console", '6', c_blue, 0, tr_null, mfb(transparent)|mfb(console)|mfb(noitem)}, -{"sewage pipe", '1', c_ltgray, 0, +{"sewage pipe", '1', c_ltgray, 0, tr_null, mfb(transparent)}, -{"sewage pump", '&', c_ltgray, 0, +{"sewage pump", '&', c_ltgray, 0, tr_null, mfb(noitem)}, -{"centrifuge", '{', c_magenta, 0, +{"centrifuge", '{', c_magenta, 0, tr_null, mfb(transparent)}, -{"column", '1', c_ltgray, 0, +{"column", '1', c_ltgray, 0, tr_null, mfb(flammable)}, -{"refrigerator", '{', c_ltcyan, 0, +{"refrigerator", '{', c_ltcyan, 0, tr_null, mfb(container)}, -{"dresser", '{', c_brown, 0, +{"dresser", '{', c_brown, 0, tr_null, mfb(transparent)|mfb(container)|mfb(flammable)}, -{"display rack", '{', c_ltgray, 0, +{"display rack", '{', c_ltgray, 0, tr_null, mfb(transparent)|mfb(container)|mfb(l_flammable)}, -{"book case", '{', c_brown, 0, +{"book case", '{', c_brown, 0, tr_null, mfb(container)|mfb(flammable)}, -{"dumpster", '{', c_green, 0, +{"dumpster", '{', c_green, 0, tr_null, mfb(container)}, -{"cloning vat", '0', c_ltcyan, 0, +{"cloning vat", '0', c_ltcyan, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(container)|mfb(sealed)}, -{"crate", '{', c_brown, 0, +{"crate", '{', c_brown, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(container)|mfb(sealed)| mfb(flammable)}, -{"open crate", '{', c_brown, 0, +{"open crate", '{', c_brown, 0, tr_null, mfb(transparent)|mfb(bashable)|mfb(container)|mfb(flammable)}, -{"stairs down", '>', c_yellow, 2, +{"stairs down", '>', c_yellow, 2, tr_null, mfb(transparent)|mfb(goes_down)|mfb(container)}, -{"stairs up", '<', c_yellow, 2, +{"stairs up", '<', c_yellow, 2, tr_null, mfb(transparent)|mfb(goes_up)|mfb(container)}, -{"manhole", '>', c_dkgray, 2, +{"manhole", '>', c_dkgray, 2, tr_null, mfb(transparent)|mfb(goes_down)|mfb(container)}, -{"ladder", '<', c_dkgray, 2, +{"ladder", '<', c_dkgray, 2, tr_null, mfb(transparent)|mfb(goes_up)|mfb(container)}, -{"ladder", '>', c_dkgray, 2, +{"ladder", '>', c_dkgray, 2, tr_null, mfb(transparent)|mfb(goes_down)|mfb(container)}, -{"downward slope", '>', c_brown, 2, +{"downward slope", '>', c_brown, 2, tr_null, mfb(transparent)|mfb(goes_down)|mfb(container)}, -{"upward slope", '<', c_brown, 2, +{"upward slope", '<', c_brown, 2, tr_null, mfb(transparent)|mfb(goes_up)|mfb(container)}, -{"rope leading up", '<', c_white, 2, +{"rope leading up", '<', c_white, 2, tr_null, mfb(transparent)|mfb(goes_up)}, -{"manhole cover", '0', c_dkgray, 2, +{"manhole cover", '0', c_dkgray, 2, tr_null, mfb(transparent)}, -{"card reader", '6', c_pink, 0, // Science +{"card reader", '6', c_pink, 0, tr_null, // Science mfb(noitem)}, -{"card reader", '6', c_pink, 0, // Military +{"card reader", '6', c_pink, 0, tr_null, // Military mfb(noitem)}, -{"broken card reader",'6',c_ltgray, 0, +{"broken card reader",'6',c_ltgray, 0, tr_null, mfb(noitem)}, -{"slot machine", '6', c_green, 0, +{"slot machine", '6', c_green, 0, tr_null, mfb(bashable)|mfb(noitem)}, -{"elevator controls",'6', c_ltblue, 0, +{"elevator controls",'6', c_ltblue, 0, tr_null, mfb(noitem)}, -{"powerless controls",'6',c_ltgray, 0, +{"powerless controls",'6',c_ltgray, 0, tr_null, mfb(noitem)}, -{"elevator", '.', c_magenta, 2, +{"elevator", '.', c_magenta, 2, tr_null, 0}, -{"dark pedestal", '&', c_dkgray, 0, +{"dark pedestal", '&', c_dkgray, 0, tr_null, mfb(transparent)}, -{"light pedestal", '&', c_white, 0, +{"light pedestal", '&', c_white, 0, tr_null, mfb(transparent)}, -{"red stone", '#', c_red, 0, +{"red stone", '#', c_red, 0, tr_null, 0}, -{"green stone", '#', c_green, 0, +{"green stone", '#', c_green, 0, tr_null, 0}, -{"blue stone", '#', c_blue, 0, +{"blue stone", '#', c_blue, 0, tr_null, 0}, -{"red floor", '.', c_red, 2, +{"red floor", '.', c_red, 2, tr_null, mfb(transparent)}, -{"green floor", '.', c_green, 2, +{"green floor", '.', c_green, 2, tr_null, mfb(transparent)}, -{"blue floor", '.', c_blue, 2, +{"blue floor", '.', c_blue, 2, tr_null, mfb(transparent)}, -{"yellow switch", '6', c_yellow, 0, +{"yellow switch", '6', c_yellow, 0, tr_null, mfb(transparent)}, -{"cyan switch", '6', c_cyan, 0, +{"cyan switch", '6', c_cyan, 0, tr_null, mfb(transparent)}, -{"purple switch", '6', c_magenta, 0, +{"purple switch", '6', c_magenta, 0, tr_null, mfb(transparent)}, -{"checkered switch", '6', c_white, 0, +{"checkered switch", '6', c_white, 0, tr_null, mfb(transparent)} }; @@ -610,6 +614,7 @@ struct submap { int active_item_count; int field_count; std::vector spawns; + std::vector vehicles; computer comp; }; diff --git a/mapgen.cpp b/mapgen.cpp index 0e12893790..1219f6e336 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -409,8 +409,8 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, } // j and i loop through appropriate hive-cell center squares - for (int j = 5; j < SEEY * 2; j += 6) { - for (int i = (j == 5 || j == 17 ? 3 : 6); i < SEEX * 2; i += 6) { + for (int j = 5; j < SEEY * 2 - 5; j += 6) { + for (int i = (j == 5 || j == 17 ? 3 : 6); i < SEEX * 2 - 5; i += 6) { if (!one_in(8)) { // Caps are always there ter(i , j - 5) = t_wax; @@ -1085,7 +1085,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, house_room(this, room_bathroom, lw, cw, lw + 3, bw); house_room(this, room_bedroom, lw + 3, cw, rw, bw); if (one_in(4)) - ter(rng(lw + 1, lw + 2), bw - 2) = t_door_c; + ter(rng(lw + 1, lw + 2), cw) = t_door_c; else ter(lw + 3, rng(cw + 2, bw - 2)) = t_door_c; rn = rng(lw + 4, rw - 2); @@ -1185,7 +1185,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, case ot_s_lot: for (int i = 0; i < SEEX * 2; i++) { - for (int j = 0; j < SEEX * 2; j++) { + for (int j = 0; j < SEEY * 2; j++) { if ((j == 5 || j == 9 || j == 13 || j == 17 || j == 21) && ((i > 1 && i < 8) || (i > 14 && i < SEEX * 2 - 2))) ter(i, j) = t_pavement_y; @@ -1196,6 +1196,15 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, ter(i, j) = grass_or_dirt(); } } + if (one_in(3)) + { + int vx = rng (0, 3) * 4 + 5; + int vy = 4; + vhtype_id vt = (one_in(10)? veh_sandbike : + (one_in(8)? veh_truck : + (one_in(3)? veh_car : veh_motorcycle))); + add_vehicle (g, vt, vx, vy, one_in(2)? 90 : 270); + } place_items(mi_road, 8, 0, 0, SEEX * 2 - 1, SEEY * 2 - 1, false, turn); if (t_east >= ot_road_null && t_east <= ot_road_nesw_manhole) rotate(1); @@ -2450,9 +2459,9 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, line(this, t_reinforced_glass_v, SEEX - 2, SEEY - 1, SEEX - 2, SEEY); line(this, t_reinforced_glass_v, SEEX + 1, SEEY - 1, SEEX + 1, SEEY); ter(SEEX - 3, SEEY - 3) = t_console; - tmpcomp = add_computer(SEEX - 3, SEEY - 3, "Bionic access", 4); + tmpcomp = add_computer(SEEX - 3, SEEY - 3, "Bionic access", 3); tmpcomp->add_option("Manifest", COMPACT_LIST_BIONICS, 0); - tmpcomp->add_option("Open Chambers", COMPACT_RELEASE, 4); + tmpcomp->add_option("Open Chambers", COMPACT_RELEASE, 5); tmpcomp->add_failure(COMPFAIL_MANHACKS); tmpcomp->add_failure(COMPFAIL_SECUBOTS); break; @@ -2587,8 +2596,8 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, break; case 3: // Supplies for (int i = by1 + 1; i <= by2 - 1; i += 3) { - line(this, t_rack, bx1 + 1, i, bx2 - 1, i); - place_items(mi_mil_food, 78, bx1 + 1, i, bx2 - 1, i, false, 0); + line(this, t_rack, bx1 + 2, i, bx2 - 2, i); + place_items(mi_mil_food, 78, bx1 + 2, i, bx2 - 2, i, false, 0); } break; } @@ -2927,7 +2936,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, std::vector next; for (int nx = x - 1; nx <= x + 1; nx++ ) { for (int ny = y; ny <= y + 1; ny++) { - if (ter(nx, ny) == t_rock_floor); + if (ter(nx, ny) == t_rock_floor) next.push_back( point(nx, ny) ); } } @@ -4949,7 +4958,7 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, line(this, t_wall_v, SEEX * 2 - 1, 0, SEEX * 2 - 1, SEEX * 2 - 1); } // Now pick a random layout - switch (rng(1, 6)) { + switch (rng(1, 4)) { case 1: // Just one. big. room. mansion_room(this, 1, tw, rw, SEEY * 2 - 2); @@ -5028,33 +5037,34 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, case 4: // One large room in lower-left mw = rng( 4, 10); cw = rng(13, 19); + x = rng(5, 10); + y = rng(13, 18); line(this, t_wall_h, 1, mw, cw, mw); - ter(rng(2, cw - 1), mw) = t_door_c; + ter( rng(x + 1, cw - 1), mw) = t_door_c; line(this, t_wall_v, cw, mw + 1, cw, SEEY * 2 - 2); - ter(cw, rng(mw + 2, SEEY * 2 - 3)) = t_door_c; + ter(cw, rng(y + 2, SEEY * 2 - 3) ) = t_door_c; mansion_room(this, 1, mw + 1, cw - 1, SEEY * 2 - 2); // And a couple small rooms in the UL LR corners - x = rng(5, 10); line(this, t_wall_v, x, tw, x, mw - 1); mansion_room(this, 1, tw, x - 1, mw - 1); if (one_in(2)) ter(rng(2, x - 2), mw) = t_door_c; else ter(x, rng(tw + 2, mw - 2)) = t_door_c; - y = rng(13, 18); line(this, t_wall_h, cw + 1, y, rw, y); mansion_room(this, cw + 1, y, rw, SEEY * 2 - 2); if (one_in(2)) ter(rng(cw + 2, rw - 1), y) = t_door_c; else - ter(rng(y + 2, SEEY * 2 - 3), cw) = t_door_c; + ter(cw, rng(y + 2, SEEY * 2 - 3)) = t_door_c; if (t_west == ot_mansion_entrance || t_west == ot_mansion) line(this, t_floor, 0, SEEY - 1, 0, SEEY); if (t_south == ot_mansion_entrance || t_south == ot_mansion) line(this, t_floor, SEEX - 1, SEEY * 2 - 1, SEEX, SEEY * 2 - 1); break; - } + } // switch (rng(1, 4)) + // Finally, place windows on outside-facing walls if necessary if (t_west != ot_mansion_entrance && t_west != ot_mansion) { int consecutive = 0; @@ -6356,6 +6366,32 @@ void map::add_spawn(monster *mon) mon->faction_id, mon->mission_id, spawnname); } +vehicle *map::add_vehicle(game *g, vhtype_id type, int x, int y, int dir) +{ + if (x < 0 || x >= SEEX * my_MAPSIZE || y < 0 || y >= SEEY * my_MAPSIZE) { + debugmsg("Bad add_vehicle t=%d d=%d x=%d y=%d", type, dir, x, y); + return 0; + } +// debugmsg("add_vehicle t=%d d=%d x=%d y=%d", type, dir, x, y); + int smx = x / SEEX; + int smy = y / SEEY; + int nonant = smx + smy * my_MAPSIZE; + x %= SEEX; + y %= SEEY; +// debugmsg("n=%d x=%d y=%d MAPSIZE=%d ^2=%d", nonant, x, y, MAPSIZE, MAPSIZE*MAPSIZE); + vehicle veh(g, type); + veh.posx = x; + veh.posy = y; + veh.smx = smx; + veh.smy = smy; + veh.face.init(dir); + veh.turn_dir = dir; + veh.precalc_mounts (0, dir); + grid[nonant].vehicles.push_back(veh); + //debugmsg ("grid[%d].vehicles.size=%d veh.parts.size=%d", nonant, grid[nonant].vehicles.size(),veh.parts.size()); + return &grid[nonant].vehicles[grid[nonant].vehicles.size()-1]; +} + computer* map::add_computer(int x, int y, std::string name, int security) { ter(x, y) = t_console; // TODO: Turn this off? @@ -6383,6 +6419,7 @@ void map::rotate(int turns) std::vector itrot[SEEX*2][SEEY*2]; std::vector sprot[my_MAPSIZE * my_MAPSIZE]; computer tmpcomp; + std::vector tmpveh; switch (turns) { case 1: @@ -6414,6 +6451,12 @@ void map::rotate(int turns) grid[my_MAPSIZE].comp = grid[my_MAPSIZE + 1].comp; grid[my_MAPSIZE + 1].comp = grid[1].comp; grid[1].comp = tmpcomp; +// ...and vehicles + tmpveh = grid[0].vehicles; + grid[0].vehicles = grid[my_MAPSIZE].vehicles; + grid[my_MAPSIZE].vehicles = grid[my_MAPSIZE + 1].vehicles; + grid[my_MAPSIZE + 1].vehicles = grid[1].vehicles; + grid[1].vehicles = tmpveh; break; case 2: @@ -6445,6 +6488,13 @@ void map::rotate(int turns) tmpcomp = grid[1].comp; grid[1].comp = grid[my_MAPSIZE].comp; grid[my_MAPSIZE].comp = tmpcomp; +// ...and vehicles + tmpveh = grid[0].vehicles; + grid[0].vehicles = grid[my_MAPSIZE + 1].vehicles; + grid[my_MAPSIZE + 1].vehicles = tmpveh; + tmpveh = grid[1].vehicles; + grid[1].vehicles = grid[my_MAPSIZE].vehicles; + grid[my_MAPSIZE].vehicles = tmpveh; break; case 3: @@ -6475,12 +6525,24 @@ void map::rotate(int turns) grid[1].comp = grid[my_MAPSIZE + 1].comp; grid[my_MAPSIZE + 1].comp = grid[my_MAPSIZE].comp; grid[my_MAPSIZE].comp = tmpcomp; +// ...and vehicles + tmpveh = grid[0].vehicles; + grid[0].vehicles = grid[1].vehicles; + grid[1].vehicles = grid[my_MAPSIZE + 1].vehicles; + grid[my_MAPSIZE + 1].vehicles = grid[my_MAPSIZE].vehicles; + grid[my_MAPSIZE].vehicles = tmpveh; break; default: return; } +// change vehicles' directions + for (int i = 0; i < my_MAPSIZE * my_MAPSIZE; i++) + for (int v = 0; v < grid[i].vehicles.size(); v++) + if (turns >= 1 && turns <= 3) + grid[i].vehicles[v].turn (turns * 90); + // Set the spawn points grid[0].spawns = sprot[0]; grid[1].spawns = sprot[1]; @@ -6881,9 +6943,9 @@ void science_room(map *m, int x1, int y1, int x2, int y2, int rotate) int compx = int((x1 + x2) / 2), compy = int((y1 + y2) / 2); m->ter(compx, compy) = t_console; - computer* tmpcomp = m->add_computer(compx, compy, "Bionic access", 4); + computer* tmpcomp = m->add_computer(compx, compy, "Bionic access", 2); tmpcomp->add_option("Manifest", COMPACT_LIST_BIONICS, 0); - tmpcomp->add_option("Open Chambers", COMPACT_RELEASE, 2); + tmpcomp->add_option("Open Chambers", COMPACT_RELEASE, 3); tmpcomp->add_failure(COMPFAIL_MANHACKS); tmpcomp->add_failure(COMPFAIL_SECUBOTS); } else { @@ -7376,8 +7438,8 @@ x: %d - %d, dx: %d cx: %d/%d\n\ x: %d - %d, dx: %d cx: %d/%d", x1, x2, dx, cx_low, cx_hi, y1, y2, dy, cy_low, cy_hi); */ - bool walled_west = (x1 == 0), walled_north = (y1 == 0), - walled_east = (x2 == SEEX * 2 - 1), walled_south = (y2 == SEEY * 2 - 1); + bool walled_west = (x1 <= 1), walled_north = (y1 == 0), + walled_east = (x2 == SEEX * 2 - 1), walled_south = (y2 >= SEEY * 2 - 2); switch (type) { @@ -7399,6 +7461,10 @@ x: %d - %d, dx: %d cx: %d/%d", x1, x2, dx, cx_low, cx_hi, if (m->ter(i, y2 + 1) != t_door_c) m->ter(i, y2) = t_shrub; } + if (walled_south && x1 <= SEEX && SEEX <= x2) { + m->ter(SEEX - 1, y2) = grass_or_dirt(); + m->ter(SEEX, y2) = grass_or_dirt(); + } } break; @@ -7630,7 +7696,7 @@ void map::add_extra(map_extra type, game *g) { int cx = rng(4, SEEX * 2 - 5), cy = rng(4, SEEY * 2 - 5); for (int x = 0; x < SEEX * 2; x++) { - for (int y = 0; y <= SEEY * 2; y++) { + for (int y = 0; y < SEEY * 2; y++) { if (x >= cx - 4 && x <= cx + 4 && y >= cy - 4 && y <= cy + 4) { if (!one_in(5)) ter(x, y) = t_wreckage; diff --git a/mapitemsdef.cpp b/mapitemsdef.cpp index e2fb5e9834..961e8e4fb6 100644 --- a/mapitemsdef.cpp +++ b/mapitemsdef.cpp @@ -26,7 +26,8 @@ void game::init_mapitems() setvector( mapitems[mi_road], - itm_muffler, itm_pipe, itm_motor, NULL); + itm_muffler, itm_pipe, itm_motor, itm_wheel, itm_big_wheel, itm_seat, + itm_combustion_small, itm_combustion, NULL); setvector( mapitems[mi_livingroom], @@ -157,7 +158,7 @@ void game::init_mapitems() setvector( mapitems[mi_hardware], itm_superglue, itm_chain, itm_rope_6, itm_rope_30, itm_glass_sheet, - itm_pipe, itm_nail, itm_hose, itm_string_36, NULL); + itm_pipe, itm_nail, itm_hose, itm_string_36, itm_frame, itm_metal_tank, NULL); setvector( mapitems[mi_tools], @@ -168,7 +169,7 @@ void game::init_mapitems() setvector( mapitems[mi_bigtools], itm_broom, itm_mop, itm_hoe, itm_shovel, itm_chainsaw_off, - itm_hammer_sledge, itm_jackhammer, NULL); + itm_hammer_sledge, itm_jackhammer, itm_welder, NULL); setvector( mapitems[mi_mischw], @@ -383,7 +384,7 @@ void game::init_mapitems() setvector( mapitems[mi_vault], itm_purifier, itm_plut_cell, itm_ftk93, itm_canister_goo, itm_UPS_off, - itm_gold, itm_bionics_super, NULL); + itm_gold, itm_bionics_super, itm_plasma_engine, itm_minireactor, itm_alloy_plate, NULL); setvector( mapitems[mi_art], @@ -396,7 +397,7 @@ void game::init_mapitems() itm_mask_gas, itm_goggles_welding, itm_goggles_nv, itm_glasses_monocle, itm_tophat, itm_ruger_redhawk, itm_deagle_44, itm_m1911, itm_geiger_off, itm_UPS_off, itm_tazer, itm_mp3, itm_fur, itm_leather, itm_string_36, - itm_chain, itm_steel_chunk, itm_manhole_cover, itm_rock, + itm_chain, itm_steel_chunk, itm_steel_lump, itm_manhole_cover, itm_rock, itm_hammer_sledge, itm_ax, itm_knife_butcher, itm_knife_combat, itm_bat, itm_petrified_eye, itm_binoculars, itm_boots, itm_mocassins, itm_dress_shoes, itm_heels, itm_pants, itm_pants_army, itm_skirt, @@ -467,10 +468,10 @@ void game::init_mapitems() mapitems[mi_electronics], itm_superglue, itm_electrohack, itm_processor, itm_RAM, itm_power_supply, itm_amplifier, itm_transponder, itm_receiver, - itm_antenna, itm_motor, itm_screwdriver, itm_mask_dust, + itm_antenna, itm_motor, itm_motor_large, itm_storage_battery, itm_screwdriver, itm_mask_dust, itm_glasses_safety, itm_goggles_welding, itm_battery, itm_plut_cell, itm_manual_electronics, itm_textbook_electronics, itm_soldering_iron, - itm_hotplate, itm_UPS_off, itm_usb_drive, itm_software_useless, NULL); + itm_hotplate, itm_UPS_off, itm_usb_drive, itm_software_useless, itm_solar_panel, NULL); setvector( mapitems[mi_monparts], @@ -656,14 +657,15 @@ void game::init_mapitems() setvector( mapitems[mi_robots], itm_processor, itm_RAM, itm_power_supply, itm_amplifier, - itm_transponder, itm_receiver, itm_antenna, itm_steel_chunk, itm_motor, + itm_transponder, itm_receiver, itm_antenna, itm_steel_chunk, itm_steel_lump, itm_motor, itm_battery, itm_plut_cell, NULL); setvector( mapitems[mi_helicopter], - itm_chain, itm_power_supply, itm_antenna, itm_steel_chunk, itm_motor, + itm_chain, itm_power_supply, itm_antenna, itm_steel_chunk, itm_steel_lump, itm_frame, + itm_steel_plate, itm_spiked_plate, itm_hard_plate, itm_motor, itm_motor_large, itm_hose, itm_pants_army, itm_jumpsuit, itm_kevlar, itm_mask_gas, - itm_helmet_army, itm_battery, itm_plut_cell, itm_m249, + itm_helmet_army, itm_battery, itm_plut_cell, itm_m249, itm_combustion_large, itm_extinguisher, itm_two_way_radio, itm_radio, itm_UPS_off, NULL); // TODO: Replace kevlar with the ceramic plate armor @@ -740,7 +742,7 @@ void game::init_mapitems() setvector( mapitems[mi_wreckage], - itm_chain, itm_steel_chunk, itm_rock, NULL); + itm_chain, itm_steel_chunk, itm_steel_lump, itm_frame, itm_rock, NULL); setvector( mapitems[mi_npc_hacker], diff --git a/melee.cpp b/melee.cpp index 43b1927aa9..44e9715509 100644 --- a/melee.cpp +++ b/melee.cpp @@ -11,6 +11,14 @@ #include #endif +void hit_message(game *g, std::string subject, std::string verb, + std::string target, int dam, bool crit); +void melee_practice(player &u, bool hit, bool unarmed, bool bashing, + bool cutting, bool stabbing); +int attack_speed(player &u, bool missed); +int stumble(player &u); +std::string melee_verb(technique_id tech, std::string your, player &p, + int bash_dam, int cut_dam, int stab_dam); /* Melee Functions! * These all belong to class player. @@ -30,24 +38,39 @@ bool player::is_armed() { - return (weapon.type->id != 0); + return (weapon.type->id != 0 && !weapon.is_style()); } bool player::unarmed_attack() { - return (weapon.type->id == 0 || weapon.type->id == itm_bio_claws); + return (weapon.type->id == 0 || weapon.is_style() || + weapon.has_flag(IF_UNARMED_WEAPON)); } - -int player::base_to_hit(bool real_life) +int player::base_to_hit(bool real_life, int stat) { - int dex = (real_life ? dex_cur : dex_max); - return 1 + int(dex / 2) + sklevel[sk_melee]; + if (stat == -999) + stat = (real_life ? dex_cur : dex_max); + return 1 + int(stat / 2) + sklevel[sk_melee]; } int player::hit_roll() { - int numdice = base_to_hit() + weapon.type->m_to_hit; + int stat = dex_cur; +// Some martial arts use something else to determine hits! + switch (weapon.type->id) { + case itm_style_tiger: + stat = (str_cur * 2 + dex_cur) / 3; + break; + case itm_style_leopard: + stat = (per_cur + int_cur + dex_cur * 2) / 4; + break; + case itm_style_snake: + stat = (per_cur + dex_cur) / 2; + break; + } + int numdice = base_to_hit(stat) + weapon.type->m_to_hit + + disease_intensity(DI_ATTACK_BOOST); int sides = 10 - encumb(bp_torso); int best_bonus = 0; if (sides < 2) @@ -81,543 +104,270 @@ int player::hit_roll() best_bonus = stab_bonus; } - numdice += best_bonus; + numdice += best_bonus; // Use whichever bonus is best. + +// Drunken master makes us hit better if (has_trait(PF_DRUNKEN)) { if (unarmed_attack()) - numdice += rng(0, 1) + int(disease_level(DI_DRUNK) / 300); + numdice += int(disease_level(DI_DRUNK) / 300); else numdice += int(disease_level(DI_DRUNK) / 400); } if (numdice < 1) { numdice = 1; - sides = 8; + sides = 8 - encumb(bp_torso); } return dice(numdice, sides); } - -int player::hit_mon(game *g, monster *z) +int player::hit_mon(game *g, monster *z, bool allow_grab) // defaults to true { bool is_u = (this == &(g->u)); // Affects how we'll display messages if (is_u) z->add_effect(ME_HIT_BY_PLAYER, 100); // Flag as attacked by us int j; bool can_see = (is_u || g->u_see(posx, posy, j)); + std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); + std::string verb = "hit"; + std::string target = "the " + z->name(); + +// If !allow_grab, then we already grabbed them--meaning their dodge is hampered + int mondodge = (allow_grab ? z->dodge_roll() : z->dodge_roll() / 3); + + bool missed = (hit_roll() < mondodge || + one_in(4 + dex_cur + weapon.type->m_to_hit)); + + int move_cost = attack_speed(*this, missed); + + if (missed) { + int stumble_pen = stumble(*this); + if (is_u) { // Only display messages if this is the player + if (weapon.has_technique(TEC_FEINT, this)) + g->add_msg("You feint."); + else if (stumble_pen >= 60) + g->add_msg("You miss and stumble with the momentum."); + else if (stumble_pen >= 10) + g->add_msg("You swing wildly and miss."); + else + g->add_msg("You miss."); + } + melee_practice(*this, false, unarmed_attack(), + weapon.is_bashing_weapon(), weapon.is_cutting_weapon(), + (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))); + move_cost += stumble_pen; + if (weapon.has_technique(TEC_FEINT, this)) + move_cost = rng(move_cost / 3, move_cost); + moves -= move_cost; + return 0; + } + moves -= move_cost; -// Types of combat (may overlap!) - bool unarmed = unarmed_attack(), - bashing = weapon.is_bashing_weapon(), - cutting = weapon.is_cutting_weapon(), - stabbing = (weapon.has_flag(IF_SPEAR) || - weapon.has_flag(IF_STAB) ); + bool critical_hit = scored_crit(mondodge); - bool can_poison = false; + int bash_dam = roll_bash_damage(z, critical_hit); + int cut_dam = roll_cut_damage(z, critical_hit); + int stab_dam = roll_stab_damage(z, critical_hit); - bool conductive = (weapon.conductive() && !wearing_something_on(bp_hands)); + int pain = 0; // Boost to pain; required for perform_technique -// Recoil penalty - if (recoil <= 30) - recoil += 6; -// Movement cost - int move_cost = weapon.attack_time() + 20 * encumb(bp_torso); - if (has_trait(PF_LIGHT_BONES)) - move_cost *= .9; - if (has_trait(PF_HOLLOW_BONES)) - move_cost *= .8; - moves -= move_cost; -// Different sizes affect your chance to hit - if (hit_roll() < z->dodge_roll() || - one_in(4 + dex_cur + weapon.type->m_to_hit)) {// A miss! - stumble(g); - return 0; - } -// For very high hit rolls, we crit! - bool critical_hit = scored_crit(z->dodge_roll()); - int dam = base_damage(true); - int cutting_penalty = 0; // Moves lost from getting a cutting weapon stuck +// Moves lost to getting your weapon stuck + int stuck_penalty = roll_stuck_penalty(z, (stab_dam >= cut_dam)); + if (weapon.is_style()) + stuck_penalty = 0; -// Drunken Master damage bonuses - if (has_trait(PF_DRUNKEN) && has_disease(DI_DRUNK)) { -// Remember, a single drink gives 600 levels of DI_DRUNK - int mindrunk, maxdrunk; - if (unarmed) { - mindrunk = disease_level(DI_DRUNK) / 600; - maxdrunk = disease_level(DI_DRUNK) / 250; - } else { - mindrunk = disease_level(DI_DRUNK) / 900; - maxdrunk = disease_level(DI_DRUNK) / 400; - } - dam += rng(mindrunk, maxdrunk); - } +// Pick one or more special attacks + technique_id technique = pick_technique(g, z, NULL, critical_hit, allow_grab); - if (unarmed) { // Unarmed bonuses - dam += rng(0, sklevel[sk_unarmed]); - if (has_trait(PF_NAILS) && z->armor_cut() == 0 && - !wearing_something_on(bp_hands)) { - dam++; - if (one_in(2)) - can_poison = true; - } - if (has_trait(PF_CLAWS) && z->armor_cut() < 6 && - !wearing_something_on(bp_hands)) { - dam += 6; - if (one_in(2)) - can_poison = true; - } - if (has_trait(PF_TALONS) && z->armor_cut() - sklevel[sk_unarmed] < 10) { - int z_armor = (z->armor_cut() - sklevel[sk_unarmed]); - if (z_armor < 0) - z_armor = 0; - dam += 10 - z_armor; - if (one_in(2)) - can_poison = true; - } - if (has_trait(PF_THORNS) && z->armor_cut() < 4 && - !wearing_something_on(bp_hands)) { - dam += 4 - z->armor_cut(); - if (one_in(2)) - can_poison = true; - } - if (has_trait(PF_SLIME_HANDS) && !z->has_flag(MF_ACIDPROOF) && - !wearing_something_on(bp_hands)) { - dam += rng(4, 6); - can_poison = true; - } - } +// Handles effects as well; not done in melee_affect_* + perform_technique(technique, g, z, NULL, bash_dam, cut_dam, stab_dam, pain); + z->speed -= int(pain / 2); - if (rng(1, 45 - dex_cur) < 2 * sklevel[sk_unarmed] && - rng(1, 65 - dex_cur) < 2 * sklevel[sk_unarmed] ) { -// Bonus unarmed attack! - if (is_u || can_see) { - switch (rng(1, 2)) { - case 1: g->add_msg("%s elbow%s the %s!", You.c_str(), (is_u ? "" : "s"), - z->name().c_str()); break; - case 2: g->add_msg("%s knee%s the %s!", You.c_str(), (is_u ? "" : "s"), - z->name().c_str()); break; - } - } - if (sklevel[sk_unarmed] >= 4) - dam += rng(1, sklevel[sk_unarmed] / 2); - else - dam++; - practice(sk_unarmed, 2); - } -// Melee skill bonus - dam += rng(0, sklevel[sk_melee]); -// Bashing damage bonus - int bash_dam = weapon.damage_bash() - z->armor_bash(), - bash_cap = 5 + str_cur + sklevel[sk_bashing]; - if (bash_dam > bash_cap)// Cap for weak characters - bash_dam = (bash_cap * 3 + bash_dam) / 4; - if (bashing) - bash_dam += rng(0, sklevel[sk_bashing] + sqrt(double(str_cur))); - if (z->has_flag(MF_PLASTIC)) - bash_dam /= rng(2, 4); - int bash_min = bash_dam / 4; - if (bash_min < sklevel[sk_bashing] ) - bash_min = sklevel[sk_bashing]; - dam += rng(bash_min, bash_dam); -// Take some moves away from the target; at this point it's skill & bash damage - z->moves -= rng(0, dam * 2); - -// Spears treat cutting damage specially. - if (weapon.has_flag(IF_SPEAR) && - weapon.damage_cut() > z->armor_cut() - 2 * sklevel[sk_stabbing]) { - int z_armor = z->armor_cut() - 2 * sklevel[sk_stabbing]; - dam += int((weapon.damage_cut() - z_armor) / 5); - if (z->speed > 100) // Bonus against fast monsters - dam += rng( int((z->speed - 100) / 10), int((z->speed - 100) / 5)); - int minstab = sklevel[sk_stabbing] * 5 + weapon.volume() * 2, - maxstab = sklevel[sk_stabbing] * 15 + weapon.volume() * 4; - int monster_penalty = rng(minstab, maxstab); - if (monster_penalty >= 150) - g->add_msg("You force the %s to the ground!", z->name().c_str()); - else if (monster_penalty >= 50) - g->add_msg("The %s is skewered and flinches!", z->name().c_str()); - z->moves -= monster_penalty; - cutting_penalty = weapon.damage_cut() * 4 + z_armor * 8 - - dice(sklevel[sk_stabbing], 10); - practice(sk_stabbing, 2); - -// Cutting damage bonus - } else if ((weapon.damage_cut() > - z->armor_cut() - int(sklevel[sk_cutting] / 2)) || - (stabbing && - weapon.damage_cut() > z->armor_cut() - 2 * sklevel[sk_stabbing])) { - - int z_armor_cut = z->armor_cut() - int(sklevel[sk_cutting] / 2); - if (z_armor_cut < 0) - z_armor_cut = 0; - int z_armor_stab = z->armor_cut() - 2 * sklevel[sk_stabbing]; - if (z_armor_stab < 0) - z_armor_stab = 0; -// Check cut dam vs. stab dam and automatically pick one - int cutdam = weapon.damage_cut() - z_armor_cut; - int stabdam = int((weapon.damage_cut() - z_armor_stab) / 5); - if (z->speed > 100) - stabdam += rng( int((z->speed - 100) / 10), int((z->speed - 100) / 5)); - if (cutdam > stabdam || !stabbing) { - dam += cutdam; - cutting_penalty = weapon.damage_cut() * 3 + z_armor_cut * 8 - - dice(sklevel[sk_cutting], 10); - } else { - dam += stabdam; - cutting_penalty = weapon.damage_cut() * 3 + z_armor_stab * 8 - - dice(sklevel[sk_stabbing], 10); - } - } +// Mutation-based attacks + perform_special_attacks(g, z, NULL, bash_dam, cut_dam, stab_dam); - if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws - cutting_penalty /= 6; // Harder to get stuck - for (int x = z->posx - 1; x <= z->posx + 1; x++) { - for (int y = z->posy - 1; y <= z->posy + 1; y++) { - if (!one_in(3)) { - if (g->m.field_at(x, y).type == fd_blood && - g->m.field_at(x, y).density < 3) - g->m.field_at(x, y).density++; - else - g->m.add_field(g, x, y, fd_blood, 1); - } - } - } +// Handles speed penalties to monster & us, etc + melee_special_effects(g, z, NULL, critical_hit, bash_dam, cut_dam, stab_dam); + +// Make a rather quiet sound, to alert any nearby monsters + if (weapon.type->id != itm_style_ninjutsu) // Ninjutsu is silent! + g->sound(posx, posy, 8, ""); + + verb = melee_verb(technique, your, *this, bash_dam, cut_dam, stab_dam); + + int dam = bash_dam + (cut_dam > stab_dam ? cut_dam : stab_dam); + + hit_message(g, You.c_str(), verb.c_str(), target.c_str(), dam, critical_hit); + + bool bashing = (bash_dam >= 10 && !unarmed_attack()); + bool cutting = (cut_dam >= 10 && cut_dam >= stab_dam); + bool stabbing = (stab_dam >= 10 && stab_dam >= cut_dam); + melee_practice(*this, true, unarmed_attack(), bashing, cutting, stabbing); + + if (allow_grab && technique == TEC_GRAB) { +// Move our weapon to a temp slot, if it's not unarmed + if (!unarmed_attack()) { + item tmpweap = remove_weapon(); + dam += hit_mon(g, z, false); // False means a second grab isn't allowed + weapon = tmpweap; + } else + dam += hit_mon(g, z, false); // False means a second grab isn't allowed } + return dam; +} -// Critical hit effects - if (critical_hit) { - bool headshot = (!z->has_flag(MF_NOHEAD) && !one_in(3)); +void player::hit_player(game *g, player &p, bool allow_grab) +{ + int j; + bool is_u = (this == &(g->u)); // Affects how we'll display messages + bool can_see = (is_u || g->u_see(posx, posy, j)); + if (is_u && p.is_npc()) { + npc* npcPtr = dynamic_cast(&p); + npcPtr->make_angry(); + } - if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) { - dam += weapon.damage_cut(); - dam += weapon.damage_cut() * double(sklevel[sk_stabbing] / 10); - practice(sk_stabbing, 5); + std::string You = (is_u ? "You" : name); + std::string Your = (is_u ? "Your" : name + "'s"); + std::string your = (is_u ? "your" : (male ? "his" : "her")); + std::string verb = "hit"; + +// Divide their dodge roll by 2 if this is a grab + int target_dodge = (allow_grab ? p.dodge_roll(g) : p.dodge_roll(g) / 2); + int hit_value = hit_roll() - target_dodge; + bool missed = (hit_roll() <= 0); + + int move_cost = attack_speed(*this, missed); + + if (missed) { + int stumble_pen = stumble(*this); + if (is_u) { // Only display messages if this is the player + if (weapon.has_technique(TEC_FEINT, this)) + g->add_msg("You feint."); + else if (stumble_pen >= 60) + g->add_msg("You miss and stumble with the momentum."); + else if (stumble_pen >= 10) + g->add_msg("You swing wildly and miss."); + else + g->add_msg("You miss."); } + melee_practice(*this, false, unarmed_attack(), + weapon.is_bashing_weapon(), weapon.is_cutting_weapon(), + (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))); + move_cost += stumble_pen; + if (weapon.has_technique(TEC_FEINT, this)) + move_cost = rng(move_cost / 3, move_cost); + moves -= move_cost; + return; + } + moves -= move_cost; - if (unarmed) { - dam += rng(1, 4) * sklevel[sk_unarmed]; - z->moves -= dam; // Stunning blow - - if (weapon.type->id == itm_bio_claws) { - if (sklevel[sk_cutting] >= 3) - dam += 5; - headshot &= z->hp < dam && one_in(2); - if (headshot && can_see) - g->add_msg("%s claws pierce the %s's skull!", Your.c_str(), - z->name().c_str()); - else if (can_see) - g->add_msg("%s claws stab straight through the %s!", Your.c_str(), - z->name().c_str()); - } else if (has_trait(PF_TALONS)) { - dam += 2; - headshot &= z->hp < dam && one_in(2); - if (headshot && can_see) - g->add_msg("%s talons tear the %s's head open!", Your.c_str(), - z->name().c_str()); - else if (can_see) - g->add_msg("%s bur%s %s talons into the %s!", You.c_str(),(is_u?"y":"ies"), - your.c_str(), z->name().c_str()); - } else { - headshot &= z->hp < dam && one_in(2); - if (headshot && can_see) - g->add_msg("%s crush%s the %s's skull in a single blow!", - You.c_str(), (is_u ? "" : "es"), z->name().c_str()); - else if (can_see) - g->add_msg("%s deliver%s a crushing punch!",You.c_str(),(is_u ? "" : "s")); - } - if (z->hp > 0 && rng(1, 5) < sklevel[sk_unarmed]) - z->add_effect(ME_STUNNED, 1 + sklevel[sk_unarmed]); + body_part bp_hit; + int side = rng(0, 1); + hit_value += rng(-10, 10); + if (hit_value >= 30) + bp_hit = bp_eyes; + else if (hit_value >= 20) + bp_hit = bp_head; + else if (hit_value >= 10) + bp_hit = bp_torso; + else if (one_in(4)) + bp_hit = bp_legs; + else + bp_hit = bp_arms; - } else { // Not unarmed + std::string target = (p.is_npc() ? p.name + "'s " : "your "); + target += body_part_name(bp_hit, side); - if (bashing) { - dam += (str_cur / 2); - int turns_stunned = int(dam / 20) + rng(0, int(sklevel[sk_bashing] / 2)); - if (turns_stunned > 6) - turns_stunned = 6; - z->add_effect(ME_STUNNED, turns_stunned); - } - if (cutting || stabbing) { - double cut_multiplier; - if (cutting) - cut_multiplier = double(sklevel[sk_cutting] / 12); - else - cut_multiplier = double(sklevel[sk_stabbing] / 5); - if (cut_multiplier > 1.5) - cut_multiplier = 1.5; - dam += cut_multiplier * weapon.damage_cut(); - headshot &= z->hp < dam; - - if (stabbing) { - if (headshot && can_see) - g->add_msg("%s %s stabs through the %s's skull!", Your.c_str(), - weapon.tname(g).c_str(), z->name().c_str()); - else if (can_see) - g->add_msg("%s stab %s %s through the %s!", You.c_str(), your.c_str(), - weapon.tname(g).c_str(), z->name().c_str()); - } else { - if (headshot && can_see) - g->add_msg("%s %s slices the %s's head off!", Your.c_str(), - weapon.tname(g).c_str(), z->name().c_str()); - else - g->add_msg("%s %s cuts the %s deeply!", Your.c_str(), - weapon.tname(g).c_str(), z->name().c_str()); - } - } else if (bashing) { - headshot &= z->hp < dam; - if (headshot && can_see) - g->add_msg("%s crush%s the %s's skull!", You.c_str(), (is_u ? "" : "es"), - z->name().c_str()); - else if (can_see) - g->add_msg("%s crush%s the %s's body!", You.c_str(), (is_u ? "" : "es"), - z->name().c_str()); - } - } // End of not-unarmed - } // End of critical hit + bool critical_hit = scored_crit(target_dodge); -// Bonus attacks! - bool shock_them = (has_bionic(bio_shock) && power_level >= 2 && unarmed && - !z->has_flag(MF_ELECTRIC) && one_in(3)); - bool drain_them = (has_bionic(bio_heat_absorb) && power_level >= 1 && - !is_armed() && z->has_flag(MF_WARM)); - if (drain_them) - power_level--; - drain_them &= one_in(2); // Only works half the time + int bash_dam = roll_bash_damage(NULL, critical_hit); + int cut_dam = roll_cut_damage(NULL, critical_hit); + int stab_dam = roll_stab_damage(NULL, critical_hit); - std::vector special_attacks = mutation_attacks(z); + technique_id tech_def = p.pick_defensive_technique(g, NULL, this); + p.perform_defensive_technique(tech_def, g, NULL, this, bp_hit, side, + bash_dam, cut_dam, stab_dam); - if (shock_them) { - power_level -= 2; - if (can_see) - g->add_msg("%s shock%s the %s!", You.c_str(), (is_u ? "" : "s"), - z->name().c_str()); - int shock = rng(2, 5); - dam += shock * rng(1, 3); - z->moves -= shock * 180; - } - if (drain_them) { - charge_power(rng(0, 4)); - if (can_see) - g->add_msg("%s drain%s the %s's body heat!", You.c_str(), (is_u ? "" : "s"), - z->name().c_str()); - dam += rng(4, 10); - z->moves -= rng(80, 120); - } + if (bash_dam + cut_dam + stab_dam <= 0) + return; // Defensive technique canceled our attack! - for (int i = 0; i < special_attacks.size(); i++) { - int spec_dam = 0; - spec_dam += special_attacks[i].bash; - if (special_attacks[i].cut > z->armor_cut()) - spec_dam += special_attacks[i].cut - z->armor_cut(); - if (special_attacks[i].stab > z->armor_cut() * .8) - spec_dam += special_attacks[i].stab - z->armor_cut() * .8; + if (critical_hit) // Crits cancel out Toad Style's armor boost + p.rem_disease(DI_ARMOR_BOOST); - if (!can_poison && one_in(2) && - (special_attacks[i].cut > z->armor_cut() || - special_attacks[i].stab > z->armor_cut() * .8)) - can_poison = true; + int pain = 0; // Boost to pain; required for perform_technique - if (spec_dam > 0) { - g->add_msg( special_attacks[i].text.c_str() ); - dam += spec_dam; - } - } +// Moves lost to getting your weapon stuck + int stuck_penalty = roll_stuck_penalty(NULL, (stab_dam >= cut_dam)); + if (weapon.is_style()) + stuck_penalty = 0; - if (can_poison && has_trait(PF_POISONOUS)) { - z->add_effect(ME_POISONED, 6); - if (is_u) - g->add_msg("You poison the %s!", z->name().c_str()); - } +// Pick one or more special attacks + technique_id technique = pick_technique(g, NULL, &p, critical_hit, allow_grab); - if (z->has_flag(MF_ELECTRIC) && conductive) { - hurtall(rng(0, 1)); - moves -= rng(0, 50); - if (is_u) - g->add_msg("Contact with the %s shocks you!", z->name().c_str()); - } +// Handles effects as well; not done in melee_affect_* + perform_technique(technique, g, NULL, &p, bash_dam, cut_dam, stab_dam, pain); + p.pain += pain; -// Make a rather quiet sound, to alert any nearby monsters - g->sound(posx, posy, 8, ""); +// Mutation-based attacks + perform_special_attacks(g, NULL, &p, bash_dam, cut_dam, stab_dam); -// Glass weapons shatter sometimes - if (weapon.made_of(GLASS) && - rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { - if (can_see) - g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); - g->sound(posx, posy, 16, ""); -// Dump its contents on the ground - for (int i = 0; i < weapon.contents.size(); i++) - g->m.add_item(posx, posy, weapon.contents[i]); - hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage - if (weapon.is_two_handed(this))// Hurt left arm too, if it was big - hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); - dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra - remove_weapon(); - } +// Handles speed penalties to monster & us, etc + melee_special_effects(g, NULL, &p, critical_hit, bash_dam, cut_dam, stab_dam); - if (dam <= 0) { - if (is_u) - g->add_msg("You hit the %s, but do no damage.", z->name().c_str()); - else if (can_see) - g->add_msg("%s's %s hits the %s, but does no damage.", You.c_str(), - weapon.tname(g).c_str(), z->name().c_str()); - practice(sk_melee, rng(2, 5)); - if (unarmed) - practice(sk_unarmed, 2); - if (bashing) - practice(sk_bashing, 2); - if (cutting) - practice(sk_cutting, 2); - if (stabbing) - practice(sk_stabbing, 2); - return 0; +// Make a rather quiet sound, to alert any nearby monsters + if (weapon.type->id != itm_style_ninjutsu) // Ninjutsu is silent! + g->sound(posx, posy, 8, ""); + + p.hit(g, bp_hit, side, bash_dam, (cut_dam > stab_dam ? cut_dam : stab_dam)); + + verb = melee_verb(technique, your, *this, bash_dam, cut_dam, stab_dam); + int dam = bash_dam + (cut_dam > stab_dam ? cut_dam : stab_dam); + hit_message(g, You.c_str(), verb.c_str(), target.c_str(), dam, critical_hit); + + bool bashing = (bash_dam >= 10 && !unarmed_attack()); + bool cutting = (cut_dam >= 10 && cut_dam >= stab_dam); + bool stabbing = (stab_dam >= 10 && stab_dam >= cut_dam); + melee_practice(*this, true, unarmed_attack(), bashing, cutting, stabbing); + + if (allow_grab && technique == TEC_GRAB) { +// Move our weapon to a temp slot, if it's not unarmed + if (p.weapon.has_technique(TEC_BREAK, &p) && + dice(p.dex_cur + p.sklevel[sk_melee], 12) > + dice(dex_cur + sklevel[sk_melee], 10)) { + if (is_u) + g->add_msg("%s break%s the grab!", target.c_str(), (p.is_npc() ? "s" : "")); + } else if (!unarmed_attack()) { + item tmpweap = remove_weapon(); + hit_player(g, p, false); // False means a second grab isn't allowed + weapon = tmpweap; + } else + hit_player(g, p, false); // False means a second grab isn't allowed } - if (is_u) - g->add_msg("You hit the %s for %d damage.", z->name().c_str(), dam); - else if (can_see) - g->add_msg("%s hits the %s with %s %s.", You.c_str(), z->name().c_str(), - (male ? "his" : "her"), - (weapon.type->id == 0 ? "fists" : weapon.tname(g).c_str())); - practice(sk_melee, rng(5, 10)); - if (unarmed) - practice(sk_unarmed, rng(5, 10)); - if (bashing) - practice(sk_bashing, rng(5, 10)); - if (cutting) - practice(sk_cutting, rng(5, 10)); - if (stabbing) - practice(sk_stabbing, rng(5, 10)); - -// Penalize the player if their cutting weapon got stuck - if (!unarmed && dam < z->hp && cutting_penalty > dice(str_cur * 2, 20)) { - if (is_u) - g->add_msg("Your %s gets stuck in the %s, pulling it out of your hands!", - weapon.tname().c_str(), z->type->name.c_str()); - if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) - z->speed *= .7; - else - z->speed *= .85; - z->add_item(remove_weapon()); - } else { - if (dam >= z->hp) { - cutting_penalty /= 2; - cutting_penalty -= rng(sklevel[sk_cutting], sklevel[sk_cutting] * 2 + 2); - } - if (cutting_penalty > 0) - moves -= cutting_penalty; - if (cutting_penalty >= 50 && is_u) - g->add_msg("Your %s gets stuck in the %s, but you yank it free.", - weapon.tname().c_str(), z->type->name.c_str()); - if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) - z->speed *= .9; + if (tech_def == TEC_COUNTER) { + if (!p.is_npc()) + g->add_msg("Counter-attack!"); + p.hit_player(g, *this); } - - return dam; } -void player::stumble(game *g) +int stumble(player &u) { - int stumble_pen = 2 * weapon.volume() + weapon.weight(); - if (has_trait(PF_DEFT)) + int stumble_pen = 2 * u.weapon.volume() + u.weapon.weight(); + if (u.has_trait(PF_DEFT)) stumble_pen = int(stumble_pen * .3) - 10; if (stumble_pen < 0) stumble_pen = 0; - if (stumble_pen > 0 && (str_cur >= 15 || dex_cur >= 21 || - one_in(16 - str_cur) || one_in(22 - dex_cur))) +// TODO: Reflect high strength bonus in newcharacter.cpp + if (stumble_pen > 0 && (u.str_cur >= 15 || u.dex_cur >= 21 || + one_in(16 - u.str_cur) || one_in(22 - u.dex_cur))) stumble_pen = rng(0, stumble_pen); - if (!is_npc()) { // Only display messages if this is the player - if (stumble_pen >= 60) - g->add_msg("You miss and stumble with the momentum."); - else if (stumble_pen >= 10) - g->add_msg("You swing wildly and miss."); - else - g->add_msg("You miss."); - } - moves -= stumble_pen; -} - -bool player::hit_player(game *g, player &p, body_part &bp, - int &hitdam, int &hitcut) -{ -// TODO: Add bionics and other bonus (e.g. heat drain, shock, etc) - if (!is_npc() && p.is_npc()) { - npc *foe = dynamic_cast(&p); - foe->make_angry(); - } -// Movement cost - moves -= weapon.attack_time() + 20 * encumb(bp_torso); - bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(), - cutting = weapon.is_cutting_weapon(); - int hitit = hit_roll() - p.dodge_roll(g); - if (hitit < 0) { // They dodged - practice(sk_melee, rng(2, 4)); - if (unarmed) - practice(sk_unarmed, 3); - if (bashing) - practice(sk_bashing, 1); - if (cutting) - practice(sk_cutting, 2); - return false; - } - - if (hitit >= 15) - bp = bp_eyes; - else if (hitit >= 12) - bp = bp_mouth; - else if (hitit >= 10) - bp = bp_head; - else if (hitit >= 6) - bp = bp_torso; - else if (hitit >= 2) - bp = bp_arms; - else - bp = bp_legs; - - hitdam = base_damage(); - - if (unarmed) {// Unarmed bonuses - hitdam += rng(0, sklevel[sk_unarmed]); - if (sklevel[sk_unarmed] >= 5) - hitdam += rng(sklevel[sk_unarmed], 3 * sklevel[sk_unarmed]); - if (has_trait(PF_TALONS)) - hitcut += 10; - if (sklevel[sk_unarmed] >= 8 && - (one_in(3) || rng(5, 20) < sklevel[sk_unarmed])) - hitdam *= rng(2, 3); - } -// Weapon adds (melee_dam / 4) to (melee_dam) - hitdam += rng(weapon.damage_bash() / 4, weapon.damage_bash()); - if (bashing) - hitdam += rng(0, sklevel[sk_bashing]) * sqrt(double(str_cur)); - - hitdam += int(pow(1.5, double(sklevel[sk_melee]))); - hitcut = weapon.damage_cut(); - if (hitcut > 0) - hitcut += int(sklevel[sk_cutting] / 3); - if (hitdam < 0) hitdam = 0; - if (hitdam > 0 || hitcut > 0) { // Practicing - practice(sk_melee, rng(5, 10)); - if (unarmed) - practice(sk_unarmed, rng(5, 10)); - if (bashing) - practice(sk_bashing, rng(5, 10)); - if (cutting) - practice(sk_cutting, rng(5, 10)); - } else { // Less practice if we missed - practice(sk_melee, rng(2, 5)); - if (unarmed) - practice(sk_unarmed, 2); - if (bashing) - practice(sk_bashing, 2); - if (cutting) - practice(sk_cutting, 3); - } - return true; + return stumble_pen; } bool player::scored_crit(int target_dodge) @@ -625,7 +375,12 @@ bool player::scored_crit(int target_dodge) bool to_hit_crit = false, dex_crit = false, skill_crit = false; int num_crits = 0; +// Weapon to-hit roll int chance = 25; + if (unarmed_attack()) { // Unarmed attack: 1/2 of unarmed skill is to-hit + for (int i = 1; i <= int(sklevel[sk_unarmed] * .5); i++) + chance += (50 / (2 + i)); + } if (weapon.type->m_to_hit > 0) { for (int i = 1; i <= weapon.type->m_to_hit; i++) chance += (50 / (2 + i)); @@ -633,16 +388,31 @@ bool player::scored_crit(int target_dodge) for (int i = 0; i > weapon.type->m_to_hit; i--) chance /= 2; } - if (rng(0, 99) < chance) + if (rng(0, 99) < chance + 4 * disease_intensity(DI_ATTACK_BOOST)) num_crits++; +// Dexterity to-hit roll +// ... except sometimes we don't use dexteiry! + int stat = dex_cur; +// Some martial arts use something else to determine hits! + switch (weapon.type->id) { + case itm_style_tiger: + stat = (str_cur * 2 + dex_cur) / 3; + break; + case itm_style_leopard: + stat = (per_cur + int_cur + dex_cur * 2) / 4; + break; + case itm_style_snake: + stat = (per_cur + dex_cur) / 2; + break; + } chance = 25; - if (dex_cur > 8) { - for (int i = 9; i <= dex_cur; i++) + if (stat > 8) { + for (int i = 9; i <= stat; i++) chance += (21 - i); // 12, 11, 10... } else { int decrease = 5; - for (int i = 7; i >= dex_cur; i--) { + for (int i = 7; i >= stat; i--) { chance -= decrease; if (i % 2 == 0) decrease--; @@ -651,7 +421,9 @@ bool player::scored_crit(int target_dodge) if (rng(0, 99) < chance) num_crits++; +// Skill level roll int best_skill = 0; + if (weapon.is_bashing_weapon() && sklevel[sk_bashing] > best_skill) best_skill = sklevel[sk_bashing]; if (weapon.is_cutting_weapon() && sklevel[sk_cutting] > best_skill) @@ -672,7 +444,7 @@ bool player::scored_crit(int target_dodge) for (int i = 3; i > best_skill; i--) chance /= 2; } - if (rng(0, 99) < chance) + if (rng(0, 99) < chance + 4 * disease_intensity(DI_ATTACK_BOOST)) num_crits++; if (num_crits == 3) @@ -691,6 +463,7 @@ int player::dodge(game *g) return 0; int ret = 4 + (dex_cur / 2); ret += sklevel[sk_dodge]; + ret += disease_intensity(DI_DODGE_BOOST); ret -= (encumb(bp_legs) / 2) + encumb(bp_torso); ret += int(current_speed(g) / 150); if (has_trait(PF_TAIL_LONG)) @@ -705,54 +478,877 @@ int player::dodge(game *g) ret--; // Penalty if we're hyuuge else if (str_max <= 5) ret++; // Bonus if we're small - if (!can_dodge) { // We already dodged this turn + if (dodges_left <= 0) { // We already dodged this turn if (rng(1, sklevel[sk_dodge] + dex_cur + 15) <= sklevel[sk_dodge] + dex_cur) ret = rng(0, ret); else ret = 0; } - can_dodge = false; + dodges_left--; +// If we're over our cap, average it with our cap if (ret > int(dex_cur / 2) + sklevel[sk_dodge] * 2) - ret = int(dex_cur / 2) + sklevel[sk_dodge] * 2; + ret = ( ret + int(dex_cur / 2) + sklevel[sk_dodge] * 2 ) / 2; return ret; } - int player::dodge_roll(game *g) { return dice(dodge(g), 6); } - -int player::base_damage(bool real_life) +int player::base_damage(bool real_life, int stat) { - int str = (real_life ? str_cur : str_max); - int dam = (real_life ? rng(0, str / 2) : str / 2); -// Bonus for strong characters - if (str > 10) - dam += int((str - 9) / 2); + if (stat == -999) + stat = (real_life ? str_cur : str_max); + int dam = (real_life ? rng(0, stat / 2) : stat / 2); +// Bonus for statong characters + if (stat > 10) + dam += int((stat - 9) / 2); // Big bonus for super-human characters - if (str > 20) - dam += int((str - 20) * 1.5); + if (stat > 20) + dam += int((stat - 20) * 1.5); return dam; } -std::vector player::mutation_attacks(monster *z) +int player::roll_bash_damage(monster *z, bool crit) { + int ret = 0; + int stat = str_cur; // Which stat determines damage? + int skill = sklevel[sk_bashing]; // Which skill determines damage? + if (unarmed_attack()) + skill = sklevel[sk_unarmed]; + + switch (weapon.type->id) { // Some martial arts change which stat + case itm_style_crane: + stat = (dex_cur * 2 + str_cur) / 3; + break; + case itm_style_snake: + stat = int(str_cur + per_cur) / 2; + break; + case itm_style_dragon: + stat = int(str_cur + int_cur) / 2; + break; + } + + ret = base_damage(true, stat); + +// Drunken Master damage bonuses + if (has_trait(PF_DRUNKEN) && has_disease(DI_DRUNK)) { +// Remember, a single drink gives 600 levels of DI_DRUNK + int mindrunk, maxdrunk; + if (unarmed_attack()) { + mindrunk = disease_level(DI_DRUNK) / 600; + maxdrunk = disease_level(DI_DRUNK) / 250; + } else { + mindrunk = disease_level(DI_DRUNK) / 900; + maxdrunk = disease_level(DI_DRUNK) / 400; + } + ret += rng(mindrunk, maxdrunk); + } + + int bash_dam = int(stat / 2) + weapon.damage_bash(), + bash_cap = 5 + stat + skill; + + if (unarmed_attack()) + bash_dam = rng(0, int(stat / 2) + sklevel[sk_unarmed]); + + if (crit) { + bash_dam *= 1.5; + bash_cap *= 2; + } + + if (bash_dam > bash_cap)// Cap for weak characters + bash_dam = (bash_cap * 3 + bash_dam) / 4; + + if (z != NULL && z->has_flag(MF_PLASTIC)) + bash_dam /= rng(2, 4); + + int bash_min = bash_dam / 4; + + bash_dam = rng(bash_min, bash_dam); + + if (bash_dam < skill + int(stat / 2)) + bash_dam = rng(bash_dam, skill + int(stat / 2)); + + ret += bash_dam; + + ret += disease_intensity(DI_DAMAGE_BOOST); + +// Finally, extra crit effects + if (crit) { + ret += int(stat / 2); + ret += skill; + if (z != NULL) + ret -= z->armor_bash() / 2; + } else if (z != NULL) + ret -= z->armor_bash(); + + return (ret < 0 ? 0 : ret); +} + +int player::roll_cut_damage(monster *z, bool crit) +{ + if (weapon.has_flag(IF_SPEAR)) + return 0; // Stabs, doesn't cut! + int z_armor_cut = (z == NULL ? 0 : z->armor_cut() - sklevel[sk_cutting] / 2); + + if (crit) + z_armor_cut /= 2; + if (z_armor_cut < 0) + z_armor_cut = 0; + + int ret = weapon.damage_cut() - z_armor_cut; + + if (unarmed_attack() && !wearing_something_on(bp_hands)) { + if (has_trait(PF_CLAWS)) + ret += 6; + if (has_trait(PF_TALONS)) + ret += 6 + (sklevel[sk_unarmed] > 8 ? 8 : sklevel[sk_unarmed]); + if (has_trait(PF_SLIME_HANDS) && (z == NULL || !z->has_flag(MF_ACIDPROOF))) + ret += rng(4, 6); + } + + if (ret <= 0) + return 0; // No negative damage! + +// 80%, 88%, 96%, 104%, 112%, 116%, 120%, 124%, 128%, 132% + if (sklevel[sk_cutting] <= 5) + ret *= double( 0.8 + 0.08 * sklevel[sk_cutting] ); + else + ret *= double( 0.92 + 0.04 * sklevel[sk_cutting] ); + + if (crit) + ret *= double( 1.0 + double(sklevel[sk_cutting] / 12) ); + + return ret; +} + +int player::roll_stab_damage(monster *z, bool crit) +{ + int ret = 0; + int z_armor = (z == NULL ? 0 : z->armor_cut() - 3 * sklevel[sk_stabbing]); + + if (crit) + z_armor /= 3; + if (z_armor < 0) + z_armor = 0; + + if (unarmed_attack() && !wearing_something_on(bp_hands)) { + ret = 0 - z_armor; + if (has_trait(PF_CLAWS)) + ret += 6; + if (has_trait(PF_NAILS) && z_armor == 0) + ret++; + if (has_trait(PF_THORNS)) + ret += 4; + } else if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) + ret = int((weapon.damage_cut() - z_armor) / 4); + else + return 0; // Can't stab at all! + + if (z != NULL && z->speed > 100) { // Bonus against fast monsters + int speed_min = (z->speed - 100) / 10, speed_max = (z->speed - 100) / 5; + int speed_dam = rng(speed_min, speed_max); + if (speed_dam > ret * 2) + speed_dam = ret * 2; + if (speed_dam > 0) + ret += speed_dam; + } + + if (ret <= 0) + return 0; // No negative stabbing! + + if (crit) { + int multiplier = double( 1.0 + double(sklevel[sk_stabbing] / 5) ); + if (multiplier > 2.5) + multiplier = 2.5; + ret *= multiplier; + } + + return ret; +} + +int player::roll_stuck_penalty(monster *z, bool stabbing) +{ + int ret = 0; + int basharm = (z == NULL ? 6 : z->armor_bash()), + cutarm = (z == NULL ? 6 : z->armor_cut()); + if (stabbing) + ret = weapon.damage_cut() * 3 + basharm * 3 + cutarm * 3 - + dice(sklevel[sk_stabbing], 10); + else + ret = weapon.damage_cut() * 4 + basharm * 5 + cutarm * 4 - + dice(sklevel[sk_cutting], 10); + + if (ret >= weapon.damage_cut() * 10) + return weapon.damage_cut() * 10; + return (ret < 0 ? 0 : ret); +} + +technique_id player::pick_technique(game *g, monster *z, player *p, + bool crit, bool allowgrab) +{ + if (z == NULL && p == NULL) + return TEC_NULL; + + std::vector possible; + bool downed = ((z != NULL && !z->has_effect(ME_DOWNED)) || + (p != NULL && !p->has_disease(DI_DOWNED)) ); + bool plastic = (z == NULL || !z->has_flag(MF_PLASTIC)); + bool mon = (z != NULL); + int base_str_req = 0; + if (z != NULL) + base_str_req = z->type->size; + else if (p != NULL) + base_str_req = 1 + (2 + p->str_cur) / 4; + + if (allowgrab) { // Check if grabs AREN'T REALLY ALLOWED + if (mon && z->has_flag(MF_PLASTIC)) + allowgrab = false; + } + + if (crit) { // Some are crit-only + + if (weapon.has_technique(TEC_SWEEP, this) && + (z == NULL || !z->has_flag(MF_FLIES)) && !downed) + possible.push_back(TEC_SWEEP); + + if (weapon.has_technique(TEC_PRECISE, this)) + possible.push_back(TEC_PRECISE); + + if (weapon.has_technique(TEC_BRUTAL, this) && !downed && + str_cur + sklevel[sk_melee] >= 4 + base_str_req) + possible.push_back(TEC_BRUTAL); + + } + + if (possible.empty()) { // Use non-crits only if any crit-onlies aren't used + + if (weapon.has_technique(TEC_DISARM, this) && !mon && + p->weapon.type->id != 0 && !p->weapon.has_flag(IF_UNARMED_WEAPON) && + dice( dex_cur + sklevel[sk_unarmed], 8) > + dice(p->dex_cur + p->sklevel[sk_melee], 10)) + possible.push_back(TEC_DISARM); + + if (weapon.has_technique(TEC_GRAB, this) && allowgrab) + possible.push_back(TEC_GRAB); + + if (weapon.has_technique(TEC_RAPID, this)) + possible.push_back(TEC_RAPID); + + if (weapon.has_technique(TEC_THROW, this) && !downed && + str_cur + sklevel[sk_melee] >= 4 + base_str_req * 4 + rng(-4, 4)) + possible.push_back(TEC_THROW); + + if (weapon.has_technique(TEC_WIDE, this)) { // Count monsters + int enemy_count = 0; + for (int x = posx - 1; x <= posx + 1; x++) { + for (int y = posy - 1; y <= posy + 1; y++) { + int mondex = g->mon_at(x, y); + if (mondex != -1) { + if (g->z[mondex].friendly == 0) + enemy_count++; + else + enemy_count -= 2; + } + int npcdex = g->npc_at(x, y); + if (npcdex != -1) { + if (g->active_npc[npcdex].attitude == NPCATT_KILL) + enemy_count++; + else + enemy_count -= 2; + } + } + } + if (enemy_count >= (possible.empty() ? 2 : 3)) { + possible.push_back(TEC_WIDE); + } + } + + } // if (possible.empty()) + + if (possible.empty()) + return TEC_NULL; + + possible.push_back(TEC_NULL); // Always a chance to not use any technique + + return possible[ rng(0, possible.size() - 1) ]; +} + +void player::perform_technique(technique_id technique, game *g, monster *z, + player *p, int &bash_dam, int &cut_dam, + int &stab_dam, int &pain) +{ + bool mon = (z != NULL); + std::string You = (is_npc() ? name : "You"); + std::string target = (mon ? "the " + z->name() : + (p->is_npc() ? p->name : "you")); + std::string s = (is_npc() ? "s" : ""); + int tarx = (mon ? z->posx : p->posx), tary = (mon ? z->posy : p->posy); + + int junk; + bool u_see = (!is_npc() || g->u_see(posx, posy, junk)); + + if (technique == TEC_RAPID) { + moves += int( attack_speed(*this, false) / 2); + return; + } + if (technique == TEC_BLOCK) { + bash_dam *= .7; + return; + } +// The rest affect our target, and thus depend on z vs. p + switch (technique) { + + case TEC_SWEEP: + if (z != NULL && !z->has_flag(MF_FLIES)) { + z->add_effect(ME_DOWNED, rng(1, 2)); + bash_dam += z->fall_damage(); + } else if (p != NULL && !p->weapon.type->id == itm_style_judo) { + p->add_disease(DI_DOWNED, rng(1, 2), g); + bash_dam += 3; + } + break; + + case TEC_PRECISE: + if (z != NULL) + z->add_effect(ME_STUNNED, rng(1, 4)); + else if (p != NULL) + p->add_disease(DI_STUNNED, rng(1, 2), g); + pain += rng(5, 8); + break; + + case TEC_BRUTAL: + if (z != NULL) { + z->add_effect(ME_STUNNED, 1); + z->knock_back_from(g, posx, posy); + } else if (p != NULL) { + p->add_disease(DI_STUNNED, 1, g); + p->knock_back_from(g, posy, posy); + } + break; + + case TEC_THROW: +// Throws are less predictable than brutal strikes. +// We knock them back from a tile adjacent to us! + if (z != NULL) { + z->add_effect(ME_DOWNED, rng(1, 2)); + z->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); + } else if (p != NULL) { + p->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); + if (!p->weapon.type->id == itm_style_judo) + p->add_disease(DI_DOWNED, rng(1, 2), g); + } + break; + + case TEC_WIDE: { + int count_hit = 0; + for (int x = posx - 1; x <= posx + 1; x++) { + for (int y = posy - 1; y <= posy + 1; y++) { + if (x != tarx || y != tary) { // Don't double-hit our target + int mondex = g->mon_at(x, y); + if (mondex != -1 && hit_roll() >= rng(0, 5) + g->z[mondex].dodge_roll()) { + count_hit++; + int dam = roll_bash_damage(&(g->z[mondex]), false) + + roll_cut_damage (&(g->z[mondex]), false); + g->z[mondex].hurt(dam); + if (u_see) + g->add_msg("%s hit%s %s for %d damage!", You.c_str(), s.c_str(), + target.c_str(), dam); + } + int npcdex = g->npc_at(x, y); + if (npcdex != -1 && + hit_roll() >= rng(0, 5) + g->active_npc[npcdex].dodge_roll(g)) { + count_hit++; + int dam = roll_bash_damage(NULL, false); + int cut = roll_cut_damage (NULL, false); + g->active_npc[npcdex].hit(g, bp_legs, 3, dam, cut); + if (u_see) + g->add_msg("%s hit%s %s for %d damage!", You.c_str(), s.c_str(), + g->active_npc[npcdex].name.c_str(), dam + cut); + } + } + } + } + if (!is_npc()) + g->add_msg("%d enemies hit!", count_hit); + } break; + + case TEC_DISARM: + g->m.add_item(p->posx, p->posy, p->remove_weapon()); + if (u_see) + g->add_msg("%s disarm%s %s!", You.c_str(), s.c_str(), target.c_str()); + break; + + } // switch (tech) +} + +technique_id player::pick_defensive_technique(game *g, monster *z, player *p) +{ + if (blocks_left == 0) + return TEC_NULL; + + int foe_melee_skill = 0; + if (z != NULL) + foe_melee_skill = z->type->melee_skill; + else if (p != NULL) + foe_melee_skill = p->dex_cur + p->sklevel[sk_melee]; + + int foe_dodge = 0; + if (z != NULL) + foe_dodge = z->dodge_roll(); + else if (p != NULL) + foe_dodge = p->dodge_roll(g); + + blocks_left--; + if (weapon.has_technique(TEC_WBLOCK_3) && + dice(dex_cur + sklevel[sk_melee], 12) > dice(foe_melee_skill, 10)) + return TEC_WBLOCK_3; + + if (weapon.has_technique(TEC_WBLOCK_2) && + dice(dex_cur + sklevel[sk_melee], 6) > dice(foe_melee_skill, 10)) + return TEC_WBLOCK_2; + + if (weapon.has_technique(TEC_WBLOCK_1) && + dice(dex_cur + sklevel[sk_melee], 3) > dice(foe_melee_skill, 10)) + return TEC_WBLOCK_1; + + if (weapon.has_technique(TEC_DEF_DISARM, this) && + z == NULL && p->weapon.type->id != 0 && + !p->weapon.has_flag(IF_UNARMED_WEAPON) && + dice( dex_cur + sklevel[sk_unarmed], 8) > + dice(p->dex_cur + p->sklevel[sk_melee], 10)) + return TEC_DEF_DISARM; + + if (weapon.has_technique(TEC_DEF_THROW, this) && + str_cur + sklevel[sk_melee] >= 4 + z->type->size * 4 + rng(-4, 4) && + hit_roll() > rng(1, 5) + foe_dodge && !one_in(3)) + return TEC_DEF_THROW; + + if (weapon.has_technique(TEC_COUNTER, this) && + hit_roll() > rng(1, 10) + foe_dodge && !one_in(3)) + return TEC_COUNTER; + + if (weapon.has_technique(TEC_BLOCK_LEGS, this) && + (hp_cur[hp_leg_l] >= 20 || hp_cur[hp_leg_r] >= 20) && + dice(dex_cur + sklevel[sk_unarmed] + sklevel[sk_melee], 13) > + dice(8 + foe_melee_skill, 10)) + return TEC_BLOCK_LEGS; + + if (weapon.has_technique(TEC_BLOCK, this) && + (hp_cur[hp_arm_l] >= 20 || hp_cur[hp_arm_r] >= 20) && + dice(dex_cur + sklevel[sk_unarmed] + sklevel[sk_melee], 16) > + dice(6 + foe_melee_skill, 10)) + return TEC_BLOCK; + + blocks_left++; // We didn't use any blocks, so give it back! + return TEC_NULL; +} + +void player::perform_defensive_technique( + technique_id technique, game *g, monster *z, player *p, + body_part &bp_hit, int &side, int &bash_dam, int &cut_dam, int &stab_dam) + +{ + int junk; + bool mon = (z != NULL); + std::string You = (is_npc() ? name : "You"); + std::string your = (is_npc() ? (male ? "his" : "her") : "your"); + std::string target = (mon ? "the " + z->name() : p->name); + bool u_see = (!is_npc() || g->u_see(posx, posy, junk)); + + switch (technique) { + case TEC_BLOCK: + case TEC_BLOCK_LEGS: { + if (technique == TEC_BLOCK) { + bp_hit = bp_arms; + if (hp_cur[hp_arm_l] >= hp_cur[hp_arm_r]) + side = 0; + else + side = 1; + } else { // Blocking with our legs + bp_hit = bp_legs; + if (hp_cur[hp_leg_l] >= hp_cur[hp_leg_r]) + side = 0; + else + side = 1; + } + if (u_see) + g->add_msg("%s block%s with %s %s.", You.c_str(), (is_npc() ? "s" : ""), + your.c_str(), body_part_name(bp_hit, side).c_str()); + bash_dam *= .5; + double reduction = 1.0; +// Special reductions for certain styles + if (weapon.type->id == itm_style_tai_chi) + reduction -= double(0.08 * double(per_cur - 6)); + if (weapon.type->id == itm_style_taekwando) + reduction -= double(0.08 * double(str_cur - 6)); + if (reduction > 1.0) + reduction = 1.0; + if (reduction < 0.3) + reduction = 0.3; + + bash_dam *= reduction; + } break; + + case TEC_WBLOCK_1: + case TEC_WBLOCK_2: + case TEC_WBLOCK_3: +// TODO: Cause weapon damage + bash_dam = 0; + cut_dam = 0; + stab_dam = 0; + if (u_see) + g->add_msg("%s block%s with %s %s.", You.c_str(), (is_npc() ? "s" : ""), + your.c_str(), weapon.tname().c_str()); + + case TEC_COUNTER: + break; // Handled elsewhere + + case TEC_DEF_THROW: + if (u_see) + g->add_msg("%s throw%s %s!", You.c_str(), (is_npc() ? "s" : ""), + target.c_str()); + bash_dam = 0; + cut_dam = 0; + stab_dam = 0; + if (mon) { + z->add_effect(ME_DOWNED, rng(1, 2)); + z->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); + } else { + p->add_disease(DI_DOWNED, rng(1, 2), g); + p->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); + } + break; + + case TEC_DEF_DISARM: + g->m.add_item(p->posx, p->posy, p->remove_weapon()); +// Re-roll damage, without our weapon + bash_dam = p->roll_bash_damage(NULL, false); + cut_dam = p->roll_cut_damage(NULL, false); + stab_dam = p->roll_stab_damage(NULL, false); + if (u_see) + g->add_msg("%s disarm%s %s!", You.c_str(), (is_npc() ? "s" : ""), + target.c_str()); + break; + + } // switch (technique) +} + +void player::perform_special_attacks(game *g, monster *z, player *p, + int &bash_dam, int &cut_dam, int &stab_dam) +{ + bool can_poison = false; + int bash_armor = (z == NULL ? 0 : z->armor_bash()); + int cut_armor = (z == NULL ? 0 : z->armor_cut()); + std::vector special_attacks = mutation_attacks(z, p); + + for (int i = 0; i < special_attacks.size(); i++) { + bool did_damage = false; + if (special_attacks[i].bash > bash_armor) { + bash_dam += special_attacks[i].bash; + did_damage = true; + } + if (special_attacks[i].cut > cut_armor) { + cut_dam += special_attacks[i].cut - cut_armor; + did_damage = true; + } + if (special_attacks[i].stab > cut_armor * .8) { + stab_dam += special_attacks[i].stab - cut_armor * .8; + did_damage = true; + } + + if (!can_poison && one_in(2) && + (special_attacks[i].cut > cut_armor || + special_attacks[i].stab > cut_armor * .8)) + can_poison = true; + + if (did_damage) + g->add_msg( special_attacks[i].text.c_str() ); + } + + if (can_poison && has_trait(PF_POISONOUS)) { + if (z != NULL) { + if (!is_npc() && !z->has_effect(ME_POISONED)) + g->add_msg("You poison the %s!", z->name().c_str()); + z->add_effect(ME_POISONED, 6); + } else if (p != NULL) { + if (!is_npc() && !p->has_disease(DI_POISON)) + g->add_msg("You poison %s!", p->name.c_str()); + p->add_disease(DI_POISON, 6, g); + } + } +} + +void player::melee_special_effects(game *g, monster *z, player *p, bool crit, + int &bash_dam, int &cut_dam, int &stab_dam) +{ + if (z == NULL && p == NULL) + return; + bool mon = (z != NULL); + int junk; + bool is_u = (!is_npc()); + bool can_see = (is_u || g->u_see(posx, posy, junk)); + std::string You = (is_u ? "You" : name); + std::string Your = (is_u ? "Your" : name + "'s"); + std::string your = (is_u ? "your" : name + "'s"); + std::string target = (mon ? "the " + z->name() : + (p->is_npc() ? p->name : "you")); + std::string target_possessive = (mon ? "the " + z->name() + "'s" : + (p->is_npc() ? p->name + "'s" : your)); + int tarposx = (mon ? z->posx : p->posx), tarposy = (mon ? z->posy : p->posy); + +// Bashing effecs + if (mon) + z->moves -= rng(0, bash_dam * 2); + else + p->moves -= rng(0, bash_dam * 2); + +// Bashing crit + if (crit && !unarmed_attack()) { + int turns_stunned = int(bash_dam / 20) + rng(0, int(sklevel[sk_bashing] / 2)); + if (turns_stunned > 6) + turns_stunned = 6; + if (turns_stunned > 0) { + if (mon) + z->add_effect(ME_STUNNED, turns_stunned); + else + p->add_disease(DI_STUNNED, turns_stunned / 2, g); + } + } + +// Stabbing effects + int stab_moves = rng(stab_dam / 2, stab_dam * 1.5); + if (crit) + stab_moves *= 1.5; + if (stab_moves >= 150) { + if (can_see) + g->add_msg("%s force%s the %s to the ground!", You.c_str(), + (is_u ? "" : "s"), target.c_str()); + if (mon) { + z->add_effect(ME_DOWNED, 1); + z->moves -= stab_moves / 2; + } else { + p->add_disease(DI_DOWNED, 1, g); + p->moves -= stab_moves / 2; + } + } else if (mon) + z->moves -= stab_moves; + else + p->moves -= stab_moves; + +// Bonus attacks! + bool shock_them = (has_bionic(bio_shock) && power_level >= 2 && + unarmed_attack() && (!mon || !z->has_flag(MF_ELECTRIC)) && + one_in(3)); + + bool drain_them = (has_bionic(bio_heat_absorb) && power_level >= 1 && + !is_armed() && (!mon || z->has_flag(MF_WARM))); + + if (drain_them) + power_level--; + drain_them &= one_in(2); // Only works half the time + + if (shock_them) { + power_level -= 2; + int shock = rng(2, 5); + if (mon) { + z->hurt( shock * rng(1, 3) ); + z->moves -= shock * 180; + if (can_see) + g->add_msg("%s shock%s %s!", You.c_str(), (is_u ? "" : "s"), + target.c_str()); + } else { + p->hurt(g, bp_torso, 0, shock * rng(1, 3)); + p->moves -= shock * 80; + } + } + + if (drain_them) { + charge_power(rng(0, 4)); + if (can_see) + g->add_msg("%s drain%s %s body heat!", You.c_str(), (is_u ? "" : "s"), + target_possessive.c_str()); + if (mon) { + z->moves -= rng(80, 120); + z->speed -= rng(4, 6); + } else + p->moves -= rng(80, 120); + } + + bool conductive = !wearing_something_on(bp_hands) && weapon.conductive(); + + if (mon && z->has_flag(MF_ELECTRIC) && conductive) { + hurtall(rng(0, 1)); + moves -= rng(0, 50); + if (is_u) + g->add_msg("Contact with the %s shocks you!", z->name().c_str()); + } + +// Glass weapons shatter sometimes + if (weapon.made_of(GLASS) && + rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { + if (can_see) + g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); + g->sound(posx, posy, 16, ""); +// Dump its contents on the ground + for (int i = 0; i < weapon.contents.size(); i++) + g->m.add_item(posx, posy, weapon.contents[i]); + hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage + if (weapon.is_two_handed(this))// Hurt left arm too, if it was big + hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); + cut_dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra + remove_weapon(); + } + +// Getting your weapon stuck + int cutting_penalty = roll_stuck_penalty(z, stab_dam > cut_dam); + if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws + cutting_penalty /= 6; // Harder to get stuck + for (int x = tarposx - 1; x <= tarposx + 1; x++) { + for (int y = tarposy - 1; y <= tarposy + 1; y++) { + if (!one_in(3)) { + if (g->m.field_at(x, y).type == fd_blood && + g->m.field_at(x, y).density < 3) + g->m.field_at(x, y).density++; + else + g->m.add_field(g, x, y, fd_blood, 1); + } + } + } + } + if (!unarmed_attack() && cutting_penalty > dice(str_cur * 2, 20)) { + if (is_u) + g->add_msg("Your %s gets stuck in %s, pulling it out of your hands!", + weapon.tname().c_str(), target.c_str()); + if (mon) { + if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) + z->speed *= .7; + else + z->speed *= .85; + z->add_item(remove_weapon()); + } else + g->m.add_item(posx, posy, remove_weapon()); + } else { + if (mon && (cut_dam >= z->hp || stab_dam >= z->hp)) { + cutting_penalty /= 2; + cutting_penalty -= rng(sklevel[sk_cutting], sklevel[sk_cutting] * 2 + 2); + } + if (cutting_penalty > 0) + moves -= cutting_penalty; + if (cutting_penalty >= 50 && is_u) + g->add_msg("Your %s gets stuck in %s, but you yank it free.", + weapon.tname().c_str(), target.c_str()); + if (mon && (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))) + z->speed *= .9; + } + +// Finally, some special effects for martial arts + switch (weapon.type->id) { + + case itm_style_karate: + dodges_left++; + blocks_left += 2; + break; + + case itm_style_aikido: + bash_dam /= 2; + break; + + case itm_style_capoeira: + add_disease(DI_DODGE_BOOST, 2, g, 2); + break; + + case itm_style_muay_thai: + if ((mon && z->type->size >= MS_LARGE) || (!mon && p->str_max >= 12)) + bash_dam += rng((mon ? z->type->size : (p->str_max - 8) / 4), + 3 * (mon ? z->type->size : (p->str_max - 8) / 4)); + break; + + case itm_style_tiger: + add_disease(DI_DAMAGE_BOOST, 2, g, 2, 10); + break; + + case itm_style_centipede: + add_disease(DI_SPEED_BOOST, 2, g, 4, 40); + break; + + case itm_style_venom_snake: + if (has_disease(DI_VIPER_COMBO)) { + if (disease_intensity(DI_VIPER_COMBO) == 1) { + if (is_u) + g->add_msg("Snakebite!"); + int dambuf = bash_dam; + bash_dam = stab_dam; + stab_dam = dambuf; + add_disease(DI_VIPER_COMBO, 2, g, 1, 2); // Upgrade to Viper Strike + } else if (disease_intensity(DI_VIPER_COMBO) == 2) { + if (hp_cur[hp_arm_l] >= hp_max[hp_arm_l] * .75 && + hp_cur[hp_arm_r] >= hp_max[hp_arm_r] * .75 ) { + if (is_u) + g->add_msg("Viper STRIKE!"); + bash_dam *= 3; + } else if (is_u) + g->add_msg("Your injured arms prevent a viper strike!"); + rem_disease(DI_VIPER_COMBO); + } + } else if (crit) { + if (is_u) + g->add_msg("Tail whip! Viper Combo Intiated!"); + bash_dam += 5; + add_disease(DI_VIPER_COMBO, 2, g, 1, 2); + } + break; + + case itm_style_scorpion: + if (crit) { + if (!is_npc()) + g->add_msg("Stinger Strike!"); + if (mon) { + z->add_effect(ME_STUNNED, 3); + int zposx = z->posx, zposy = z->posy; + z->knock_back_from(g, posx, posy); + if (z->posx != zposx || z->posy != zposy) + z->knock_back_from(g, posx, posy); // Knock a 2nd time if the first worked + } else { + p->add_disease(DI_STUNNED, 2, g); + int pposx = p->posx, pposy = p->posy; + p->knock_back_from(g, posx, posy); + if (p->posx != pposx || p->posy != pposy) + p->knock_back_from(g, posx, posy); // Knock a 2nd time if the first worked + } + } + break; + + case itm_style_zui_quan: + dodges_left = 50; // Basically, unlimited. + break; + + } +} + +std::vector player::mutation_attacks(monster *z, player *p) +{ + std::vector ret; + + if (z == NULL && p == NULL) + return ret; + + bool mon = (z != NULL); bool is_u = (!is_npc());// Affects how we'll display messages std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); + std::string target = (mon ? "the " + z->name() : p->name); - std::vector ret; std::stringstream text; if (has_trait(PF_FANGS) && !wearing_something_on(bp_mouth) && one_in(20 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; - text << You << " sink" << (is_u ? " " : "s ") << your << " fangs into the " << - z->name() << "!"; + text << You << " sink" << (is_u ? " " : "s ") << your << " fangs into " << + target << "!"; tmp.text = text.str(); tmp.stab = 20; ret.push_back(tmp); @@ -760,8 +1356,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_MANDIBLES) && one_in(22 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; - text << You << " slice" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " mandibles!"; + text << You << " slice" << (is_u ? " " : "s ") << target << " with " << + your << " mandibles!"; tmp.text = text.str(); tmp.cut = 12; ret.push_back(tmp); @@ -769,7 +1365,7 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_BEAK) && one_in(15 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; - text << You << " peck" << (is_u ? " " : "s ") << "the " << z->name() << "!"; + text << You << " peck" << (is_u ? " " : "s ") << target << "!"; tmp.text = text.str(); tmp.stab = 15; ret.push_back(tmp); @@ -777,8 +1373,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_HOOVES) && one_in(25 - dex_cur - 2 * sklevel[sk_unarmed])) { special_attack tmp; - text << You << " kick" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " hooves!"; + text << You << " kick" << (is_u ? " " : "s ") << target << " with " << + your << " hooves!"; tmp.text = text.str(); tmp.bash = str_cur * 3; if (tmp.bash > 40) @@ -788,8 +1384,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_HORNS) && one_in(20 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; - text << You << " headbutt" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " horns!"; + text << You << " headbutt" << (is_u ? " " : "s ") << target << " with " << + your << " horns!"; tmp.text = text.str(); tmp.bash = 3; tmp.stab = 3; @@ -798,8 +1394,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_HORNS_CURLED) && one_in(20 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; - text << You << " headbutt" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " curled horns!"; + text << You << " headbutt" << (is_u ? " " : "s ") << target << " with " << + your << " curled horns!"; tmp.text = text.str(); tmp.bash = 14; ret.push_back(tmp); @@ -807,8 +1403,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_HORNS_POINTED) && one_in(22 - dex_cur - sklevel[sk_unarmed])){ special_attack tmp; - text << You << " stab" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " pointed horns!"; + text << You << " stab" << (is_u ? " " : "s ") << target << " with " << + your << " pointed horns!"; tmp.text = text.str(); tmp.stab = 24; ret.push_back(tmp); @@ -816,8 +1412,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_ANTLERS) && one_in(20 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; - text << You << " butt" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " antlers!"; + text << You << " butt" << (is_u ? " " : "s ") << target << " with " << + your << " antlers!"; tmp.text = text.str(); tmp.bash = 4; ret.push_back(tmp); @@ -825,8 +1421,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_TAIL_STING) && one_in(3) && one_in(10 - dex_cur)) { special_attack tmp; - text << You << " sting" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " tail!"; + text << You << " sting" << (is_u ? " " : "s ") << target << " with " << + your << " tail!"; tmp.text = text.str(); tmp.stab = 20; ret.push_back(tmp); @@ -834,8 +1430,8 @@ std::vector player::mutation_attacks(monster *z) if (has_trait(PF_TAIL_CLUB) && one_in(3) && one_in(10 - dex_cur)) { special_attack tmp; - text << You << " hit" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " tail!"; + text << You << " hit" << (is_u ? " " : "s ") << target << " with " << + your << " tail!"; tmp.text = text.str(); tmp.bash = 18; ret.push_back(tmp); @@ -855,8 +1451,8 @@ std::vector player::mutation_attacks(monster *z) if (one_in(18 - dex_cur - sklevel[sk_unarmed])) { special_attack tmp; text.str(""); - text << You << " slap" << (is_u ? " " : "s ") << "the " << z->name() << - " with " << your << " tentacle!"; + text << You << " slap" << (is_u ? " " : "s ") << target << " with " << + your << " tentacle!"; tmp.text = text.str(); tmp.bash = str_cur / 2; ret.push_back(tmp); @@ -866,3 +1462,127 @@ std::vector player::mutation_attacks(monster *z) return ret; } + +std::string melee_verb(technique_id tech, std::string your, player &p, + int bash_dam, int cut_dam, int stab_dam) +{ + if (tech != TEC_NULL && p.weapon.is_style() && + p.weapon.style_data(tech).name != "") + return p.weapon.style_data(tech).name; + + std::stringstream ret; + + std::string s = (p.is_npc() ? "s" : ""); + + switch (tech) { + + case TEC_SWEEP: + ret << "sweep" << s << "" << s << " " << your << " " << p.weapon.tname() << + " at"; + break; + + case TEC_PRECISE: + ret << "jab" << s << " " << your << " " << p.weapon.tname() << " at"; + break; + + case TEC_BRUTAL: + ret << "slam" << s << " " << your << " " << p.weapon.tname() << " against"; + break; + + case TEC_GRAB: + ret << "wrap" << s << " " << your << " " << p.weapon.tname() << " around"; + break; + + case TEC_WIDE: + ret << "swing" << s << " " << your << " " << p.weapon.tname() << " wide at"; + break; + + case TEC_THROW: + ret << "use" << s << " " << your << " " << p.weapon.tname() << " to toss"; + break; + + default: // No tech, so check our damage levels + if (bash_dam >= cut_dam && bash_dam >= stab_dam) { + if (bash_dam >= 30) + return "clobber" + s; + if (bash_dam >= 20) + return "batter" + s; + if (bash_dam >= 10) + return "whack" + s; + return "hit" + s; + } + if (cut_dam >= stab_dam) { + if (cut_dam >= 30) + return "hack" + s; + if (cut_dam >= 20) + return "slice" + s; + if (cut_dam >= 10) + return "cut" + s; + return "nick" + s; + } +// Only stab damage is left + if (stab_dam >= 30) + return "impale" + s; + if (stab_dam >= 20) + return "pierce" + s; + if (stab_dam >= 10) + return "stab" + s; + return "poke" + s; + } // switch (tech) + + return ret.str(); +} + + +void hit_message(game *g, std::string subject, std::string verb, + std::string target, int dam, bool crit) +{ + if (dam <= 0) + g->add_msg("%s %s %s but do%s no damage.", subject.c_str(), verb.c_str(), + target.c_str(), (subject == "You" ? "" : "es")); + else + g->add_msg("%s%s %s %s for %d damage.", (crit ? "Critical! " : ""), + subject.c_str(), verb.c_str(), target.c_str(), dam); +} + +void melee_practice(player &u, bool hit, bool unarmed, bool bashing, + bool cutting, bool stabbing) +{ + if (!hit) { + u.practice(sk_melee, rng(2, 5)); + if (unarmed) + u.practice(sk_unarmed, 2); + if (bashing) + u.practice(sk_bashing, 2); + if (cutting) + u.practice(sk_cutting, 2); + if (stabbing) + u.practice(sk_stabbing, 2); + } else { + u.practice(sk_melee, rng(5, 10)); + if (unarmed) + u.practice(sk_unarmed, rng(5, 10)); + if (bashing) + u.practice(sk_bashing, rng(5, 10)); + if (cutting) + u.practice(sk_cutting, rng(5, 10)); + if (stabbing) + u.practice(sk_stabbing, rng(5, 10)); + } +} + +int attack_speed(player &u, bool missed) +{ + int move_cost = u.weapon.attack_time() + 20 * u.encumb(bp_torso); + if (u.has_trait(PF_LIGHT_BONES)) + move_cost *= .9; + if (u.has_trait(PF_HOLLOW_BONES)) + move_cost *= .8; + + move_cost -= u.disease_intensity(DI_SPEED_BOOST); + + if (move_cost < 25) + return 25; + + return move_cost; +} diff --git a/mission.cpp b/mission.cpp index f544eeeb23..5df6118cbc 100644 --- a/mission.cpp +++ b/mission.cpp @@ -26,6 +26,45 @@ std::string mission::name() return type->name; } +std::string mission::save_info() +{ + std::stringstream ret; + if (type == NULL) + ret << -1; + else + ret << type->id; + ret << description << " <> " << (failed ? 1 : 0) << " " << value << + " " << reward.type << " " << reward.value << " " << reward.item_id << + " " << reward.skill_id << " " << uid << " " << target.x << " " << + target.y << " " << item_id << " " << count << " " << deadline << " " << + npc_id << " " << good_fac_id << " " << bad_fac_id << " " << step << + " " << follow_up; + + return ret.str(); +} + +void mission::load_info(game *g, std::ifstream &data) +{ + int type_id, rewtype, reward_id, rew_item, rew_skill, itemid, tmpfollow; + data >> type_id; + type = &(g->mission_types[type_id]); + std::string tmpdesc; + do { + data >> tmpdesc; + if (tmpdesc != "<>") + description += tmpdesc + " "; + } while (tmpdesc != "<>"); + description = description.substr( 0, description.size() - 1 ); // Ending ' ' + data >> failed >> value >> rewtype >> reward_id >> rew_item >> rew_skill >> + uid >> target.x >> target.y >> itemid >> count >> deadline >> npc_id >> + good_fac_id >> bad_fac_id >> step >> tmpfollow; + follow_up = mission_id(tmpfollow); + reward.type = npc_favor_type(reward_id); + reward.item_id = itype_id( rew_item ); + reward.skill_id = skill( rew_skill ); + item_id = itype_id(itemid); +} + std::string mission_dialogue (mission_id id, talk_topic state) { switch (id) { diff --git a/mission.h b/mission.h index b66dc5075d..1b61ba3c8f 100644 --- a/mission.h +++ b/mission.h @@ -3,6 +3,7 @@ #include #include +#include #include "itype.h" #include "texthash.h" #include "npc.h" @@ -18,6 +19,7 @@ enum mission_id { MISSION_GET_ZOMBIE_BLOOD_ANAL, MISSION_RESCUE_DOG, MISSION_KILL_ZOMBIE_MOM, + MISSION_REACH_SAFETY, NUM_MISSION_IDS }; @@ -27,6 +29,7 @@ enum mission_origin { ORIGIN_NULL = 0, ORIGIN_GAME_START, // Given when the game starts ORIGIN_OPENER_NPC, // NPC comes up to you when the game starts + ORIGIN_ANY_NPC, // Any NPC ORIGIN_SECONDARY, // Given at the end of another mission NUM_ORIGIN }; @@ -62,6 +65,8 @@ struct mission_start { void place_zombie_mom (game *, mission *); // Put a zombie mom in a house! void place_npc_software(game *, mission *); // Put NPC-type-dependent software void reveal_hospital (game *, mission *); // Reveal the nearest hospital + void find_safety (game *, mission *); // Goal is set to non-spawn area + void place_book (game *, mission *); // Place a book to retrieve }; struct mission_end { // These functions are run when a mission ends @@ -80,6 +85,7 @@ struct mission_type { mission_goal goal; // The basic goal type int difficulty; // Difficulty; TODO: come up with a scale int value; // Value; determines rewards and such + npc_favor special_reward; // If we have a special gift, not cash value int deadline_low, deadline_high; // Low and high deadlines (turn numbers) bool urgent; // If true, the NPC will press this mission! @@ -114,7 +120,8 @@ struct mission { mission_type *type; std::string description; // Basic descriptive text bool failed; // True if we've failed it! - int value; + int value; // Cash/Favor value of completing this + npc_favor reward; // If there's a special reward for completing it int uid; // Unique ID number, used for referencing elsewhere point target; // Marked on the player's map. (-1,-1) for none itype_id item_id; // Item that needs to be found (or whatever) @@ -127,6 +134,9 @@ struct mission { text_hash text; std::string name(); + std::string save_info(); + void load_info(game *g, std::ifstream &info); + mission() { type = NULL; diff --git a/mission_start.cpp b/mission_start.cpp index 82691c98d3..a24a136d63 100644 --- a/mission_start.cpp +++ b/mission_start.cpp @@ -215,3 +215,39 @@ void mission_start::reveal_hospital(game *g, mission *miss) } miss->target = place; } + +void mission_start::find_safety(game *g, mission *miss) +{ + point place = g->om_location(); + bool done = false; + for (int radius = 0; radius <= 20 && !done; radius++) { + for (int dist = 0 - radius; dist <= radius && !done; dist++) { + int offset = rng(0, 3); // Randomizes the direction we check first + for (int i = 0; i <= 3 && !done; i++) { // Which direction? + point check = place; + switch ( (offset + i) % 4 ) { + case 0: check.x += dist; check.y -= radius; break; + case 1: check.x += dist; check.y += radius; break; + case 2: check.y += dist; check.x -= radius; break; + case 3: check.y += dist; check.x += radius; break; + } + if (g->cur_om.is_safe(check.x, check.y)) { + miss->target = check; + done = true; + } + } + } + } + if (!done) { // Couldn't find safety; so just set the target to far away + switch ( rng(0, 3) ) { + case 0: miss->target = point(place.x - 20, place.y - 20); break; + case 1: miss->target = point(place.x - 20, place.y + 20); break; + case 2: miss->target = point(place.x + 20, place.y - 20); break; + case 3: miss->target = point(place.x + 20, place.y + 20); break; + } + } +} + +void mission_start::place_book(game *g, mission *miss) +{ +} diff --git a/missiondef.cpp b/missiondef.cpp index 8008522e7d..f54261d03a 100644 --- a/missiondef.cpp +++ b/missiondef.cpp @@ -8,7 +8,7 @@ void game::init_missions() id++; mission_types.push_back( \ mission_type(id, name, goal, diff, val, urgent, place, start, end, fail) ) - #define ORIGINS(...) setvector(mission_types[id].origins, __VA_ARGS__) + #define ORIGINS(...) setvector(mission_types[id].origins, __VA_ARGS__, NULL) #define ITEM(itid) mission_types[id].item_id = itid // DEADLINE defines the low and high end time limits, in hours @@ -28,14 +28,14 @@ mission_type(id, name, goal, diff, val, urgent, place, start, end, fail) ) MISSION("Find Antibiotics", MGOAL_FIND_ITEM, 2, 1500, true, &mission_place::always, &mission_start::infect_npc, &mission_end::heal_infection, &mission_fail::kill_npc); - ORIGINS(ORIGIN_OPENER_NPC, NULL); + ORIGINS(ORIGIN_OPENER_NPC); ITEM(itm_antibiotics); DEADLINE(24, 48); // 1 - 2 days MISSION("Retrieve Software", MGOAL_FIND_ANY_ITEM, 2, 800, false, &mission_place::near_town, &mission_start::place_npc_software, &mission_end::standard, &mission_fail::standard); - ORIGINS(ORIGIN_OPENER_NPC, NULL); + ORIGINS(ORIGIN_OPENER_NPC, ORIGIN_ANY_NPC); MISSION("Analyze Zombie Blood", MGOAL_FIND_ITEM, 8, 2500, false, &mission_place::always, &mission_start::reveal_hospital, @@ -46,10 +46,20 @@ mission_type(id, name, goal, diff, val, urgent, place, start, end, fail) ) MISSION("Find Lost Dog", MGOAL_FIND_MONSTER, 3, 1000, false, &mission_place::near_town, &mission_start::place_dog, &mission_end::standard, &mission_fail::standard); - ORIGINS(ORIGIN_OPENER_NPC, NULL); + ORIGINS(ORIGIN_OPENER_NPC); MISSION("Kill Zombie Mom", MGOAL_KILL_MONSTER, 5, 1200, true, &mission_place::near_town, &mission_start::place_zombie_mom, &mission_end::standard, &mission_fail::standard); - ORIGINS(ORIGIN_OPENER_NPC, NULL); + ORIGINS(ORIGIN_OPENER_NPC, ORIGIN_ANY_NPC); + + MISSION("Reach Safety", MGOAL_GO_TO, 1, 0, false, + &mission_place::always, &mission_start::find_safety, + &mission_end::standard, &mission_fail::standard); + ORIGINS(ORIGIN_NULL); + + MISSION("Find a Book", MGOAL_FIND_ANY_ITEM, 2, 800, false, + &mission_place::always, &mission_start::place_book, + &mission_end::standard, &mission_fail::standard); + ORIGINS(ORIGIN_ANY_NPC); } diff --git a/monattack.cpp b/monattack.cpp index 7572767865..102bf126a0 100644 --- a/monattack.cpp +++ b/monattack.cpp @@ -1127,11 +1127,11 @@ void mattack::smg(game *g, monster *z) int t, j, fire_t; if (z->friendly != 0) { // Attacking monsters, not the player! monster* target = NULL; - int closest = 13; + int closest = 19; for (int i = 0; i < g->z.size(); i++) { int dist = rl_dist(z->posx, z->posy, g->z[i].posx, g->z[i].posy); if (g->z[i].friendly == 0 && dist < closest && - g->m.sees(z->posx, z->posy, g->z[i].posx, g->z[i].posy, 12, t)) { + g->m.sees(z->posx, z->posy, g->z[i].posx, g->z[i].posy, 18, t)) { target = &(g->z[i]); closest = dist; fire_t = t; @@ -1164,7 +1164,7 @@ void mattack::smg(game *g, monster *z) } // Not friendly; hence, firing at the player - if (trig_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 12 || + if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 24 || !g->sees_u(z->posx, z->posy, t)) return; z->sp_timeout = z->type->sp_freq; // Reset timer @@ -1219,9 +1219,13 @@ void mattack::copbot(game *g, monster *z) z->sp_timeout = z->type->sp_freq; // Reset timer if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 2 || !sees_u) { if (one_in(3)) { - if (sees_u) - g->sound(z->posx, z->posy, 18, "a robotic voice boom, \"Citizen, Halt!\""); - else + if (sees_u) { + if (g->u.unarmed_attack()) + g->sound(z->posx, z->posy, 18, "a robotic voice boom, \"Citizen, Halt!\""); + else + g->sound(z->posx, z->posy, 18, "a robotic voice boom, \"\ +Please put down your weapon.\""); + } else g->sound(z->posx, z->posy, 18, "a robotic voice boom, \"Come out with your hands up!\""); } else @@ -1277,3 +1281,47 @@ void mattack::generator(game *g, monster *z) if (int(g->turn) % 10 == 0 && z->hp < z->type->hp) z->hp++; } + +void mattack::upgrade(game *g, monster *z) +{ + std::vector targets; + for (int i = 0; i < g->z.size(); i++) { + if (g->z[i].type->id == mon_zombie && + rl_dist(z->posx, z->posy, g->z[i].posx, g->z[i].posy) <= 5) + targets.push_back(i); + } + if (targets.empty()) + return; + z->sp_timeout = z->type->sp_freq; // Reset timer + z->moves -= 150; // It takes a while + + monster *target = &( g->z[ targets[ rng(0, targets.size()-1) ] ] ); + + mon_id newtype = mon_zombie; + + switch( rng(1, 10) ) { + case 1: newtype = mon_zombie_shrieker; + break; + case 2: + case 3: newtype = mon_zombie_spitter; + break; + case 4: + case 5: newtype = mon_zombie_electric; + break; + case 6: + case 7: + case 8: newtype = mon_zombie_fast; + break; + case 9: newtype = mon_zombie_brute; + break; + case 10: newtype = mon_boomer; + break; + } + + target->poly(g->mtypes[newtype]); + int junk; + if (g->u_see(z->posx, z->posy, junk)) + g->add_msg("The black mist around the %s grows...", z->name().c_str()); + if (g->u_see(target->posx, target->posy, junk)) + g->add_msg("...a zombie becomes a %s!", target->name().c_str()); +} diff --git a/monattack.h b/monattack.h index fe4f2cf29c..beea39e7f9 100644 --- a/monattack.h +++ b/monattack.h @@ -42,6 +42,7 @@ class mattack void multi_robot (game *g, monster *z); // Pick from tazer, smg, flame void ratking (game *g, monster *z); void generator (game *g, monster *z); + void upgrade (game *g, monster *z); }; #endif diff --git a/mongroup.h b/mongroup.h index 150a78d8f5..30ee15ba63 100644 --- a/mongroup.h +++ b/mongroup.h @@ -26,6 +26,8 @@ enum moncat_id { num_moncats }; +bool moncat_is_safe(moncat_id id); + struct mongroup { moncat_id type; int posx, posy; @@ -41,6 +43,7 @@ struct mongroup { population = ppop; dying = false; } + bool is_safe() { return moncat_is_safe(type); }; }; #endif diff --git a/mongroupdef.cpp b/mongroupdef.cpp index 7afcef562d..5ed86cfa00 100644 --- a/mongroupdef.cpp +++ b/mongroupdef.cpp @@ -20,10 +20,11 @@ void game::init_moncats() moncats[mcat_zombie], mon_zombie, mon_zombie_shrieker, mon_zombie_spitter, mon_zombie_fast, mon_zombie_electric, mon_zombie_brute, mon_zombie_hulk, - mon_zombie_necro, mon_boomer, mon_skeleton, NULL); + mon_zombie_necro, mon_boomer, mon_skeleton, mon_zombie_grabber, + mon_zombie_master, NULL); setvector( moncats[mcat_triffid], - mon_triffid, mon_triffid_young, mon_triffid_queen, NULL); + mon_triffid, mon_triffid_young, mon_vinebeast, mon_triffid_queen, NULL); setvector( moncats[mcat_fungi], mon_fungaloid, mon_fungaloid_dormant, mon_ant_fungus, mon_zombie_fungus, @@ -66,3 +67,9 @@ void game::init_moncats() mon_tripod, mon_chickenbot, mon_tankbot, NULL); } +bool moncat_is_safe(moncat_id id) +{ + if (id == mcat_null || id == mcat_forest) + return true; + return false; +} diff --git a/monmove.cpp b/monmove.cpp index cbb2dbd78c..afa76384e0 100644 --- a/monmove.cpp +++ b/monmove.cpp @@ -196,6 +196,10 @@ void monster::move(game *g) moves = 0; return; } + if (has_effect(ME_DOWNED)) { + moves = 0; + return; + } if (friendly != 0) { if (friendly > 0) friendly--; @@ -459,7 +463,7 @@ point monster::sound_move(game *g) return next; } -void monster::hit_player(game *g, player &p) +void monster::hit_player(game *g, player &p, bool can_grab) { if (type->melee_dice == 0) // We don't attack, so just return return; @@ -470,17 +474,23 @@ void monster::hit_player(game *g, player &p) int junk; bool u_see = (!is_npc || g->u_see(p.posx, p.posy, junk)); std::string you = (is_npc ? p.name : "you"); + std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int side = rng(0, 1); - int dam = hit(g, p, bphit); + int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; + technique_id tech = p.pick_defensive_technique(g, this, NULL); + p.perform_defensive_technique(tech, g, this, NULL, bphit, side, + dam, cut, stab); if (dam == 0 && u_see) g->add_msg("The %s misses %s.", name().c_str(), you.c_str()); else if (dam > 0) { - if (u_see) + if (u_see && tech != TEC_BLOCK) g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); +// Attempt defensive moves + if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) g->add_msg("You stop reloading."); @@ -502,7 +512,11 @@ void monster::hit_player(game *g, player &p) (g->u.has_trait(PF_QUILLS) ? "quills" : "spines")); hurt(spine); } - p.hit(g, bphit, side, dam, type->melee_cut); + + if (dam + cut <= 0) + return; // Defensive technique canceled damage. + + p.hit(g, bphit, side, dam, cut); if (has_flag(MF_VENOM)) { if (!is_npc) g->add_msg("You're poisoned!"); @@ -512,7 +526,23 @@ void monster::hit_player(game *g, player &p) g->add_msg("You feel poison flood your body, wracking you with pain..."); p.add_disease(DI_BADPOISON, 40, g); } - } + if (can_grab && has_flag(MF_GRABS) && + dice(type->melee_dice, 10) > dice(p.dodge(g), 10)) { + if (!is_npc) + g->add_msg("The %s grabs you!", name().c_str()); + if (p.weapon.has_technique(TEC_BREAK, &p) && + dice(p.dex_cur + p.sklevel[sk_melee], 12) > dice(type->melee_dice, 10)){ + if (!is_npc) + g->add_msg("You break the grab!"); + } else + hit_player(g, p, false); + } + + if (tech == TEC_COUNTER && !is_npc) { + g->add_msg("Counter-attack!"); + hurt( p.hit_mon(g, this) ); + } + } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast(&p); @@ -623,6 +653,86 @@ void monster::stumble(game *g, bool moved) } } +void monster::knock_back_from(game *g, int x, int y) +{ + if (x == posx && y == posy) + return; // No effect + point to(posx, posy); + if (x < posx) + to.x++; + if (x > posx) + to.x--; + if (y < posy) + to.y++; + if (y > posy) + to.y--; + + int t = 0; + bool u_see = g->u_see(to.x, to.y, t); + +// First, see if we hit another monster + int mondex = g->mon_at(to.x, to.y); + if (mondex != -1) { + monster *z = &(g->z[mondex]); + hurt(z->type->size); + add_effect(ME_STUNNED, 1); + if (type->size > 1 + z->type->size) { + z->knock_back_from(g, posx, posy); // Chain reaction! + z->hurt(type->size); + z->add_effect(ME_STUNNED, 1); + } else if (type->size > z->type->size) { + z->hurt(type->size); + z->add_effect(ME_STUNNED, 1); + } + + if (u_see) + g->add_msg("The %s bounces off a %s!", name().c_str(), z->name().c_str()); + + return; + } + + int npcdex = g->npc_at(to.x, to.y); + if (npcdex != -1) { + npc *p = &(g->active_npc[npcdex]); + hurt(3); + add_effect(ME_STUNNED, 1); + p->hit(g, bp_torso, 0, type->size, 0); + if (u_see) + g->add_msg("The %s bounces off %s!", name().c_str(), p->name.c_str()); + + return; + } + +// If we're still in the function at this point, we're actually moving a tile! + if (g->m.move_cost(to.x, to.y) == 0) { // Wait, it's a wall (or water) + + if (g->m.has_flag(liquid, to.x, to.y)) { + if (!has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC)) { + hurt(9999); + if (u_see) + g->add_msg("The %s drowns!", name().c_str()); + } + + } else if (has_flag(MF_AQUATIC)) { // We swim but we're NOT in water + hurt(9999); + if (u_see) + g->add_msg("The %s flops around and dies!", name().c_str()); + + } else { // It's some kind of wall. + hurt(type->size); + add_effect(ME_STUNNED, 2); + if (u_see) + g->add_msg("The %s bounces off a %s.", name().c_str(), + g->m.tername(to.x, to.y).c_str()); + } + + } else { // It's no wall + posx = to.x; + posy = to.y; + } +} + + /* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; diff --git a/monster.cpp b/monster.cpp index 7c246830c9..f2f463ebac 100644 --- a/monster.cpp +++ b/monster.cpp @@ -84,8 +84,8 @@ monster::monster(mtype *t, int x, int y) spawnposx = -1; spawnposy = -1; friendly = 0; - anger = 0; - morale = 0; + anger = type->agro; + morale = type->morale; faction_id = -1; mission_id = -1; dead = false; @@ -117,6 +117,11 @@ monster::~monster() std::string monster::name() { + if (!type) + { + debugmsg ("monster::name empty type!"); + return std::string(); + } if (unique_name != "") return type->name + ": " + unique_name; return type->name; @@ -143,24 +148,30 @@ void monster::print_info(game *g, WINDOW* w) mvwprintz(w, 6, 1, c_white, "%s ", type->name.c_str()); switch (attitude(&(g->u))) { case MATT_FRIEND: - wprintz(w, h_white, "Friendly!"); + wprintz(w, h_white, "Friendly! "); break; case MATT_FLEE: - wprintz(w, c_green, "Fleeing!"); + wprintz(w, c_green, "Fleeing! "); break; case MATT_IGNORE: - wprintz(w, c_ltgray, "Ignoring"); + wprintz(w, c_ltgray, "Ignoring "); break; case MATT_FOLLOW: - wprintz(w, c_yellow, "Tracking"); + wprintz(w, c_yellow, "Tracking "); break; case MATT_ATTACK: - wprintz(w, c_red, "Hostile!"); + wprintz(w, c_red, "Hostile! "); break; default: - wprintz(w, h_red, "BUG: Behavior unnamed"); + wprintz(w, h_red, "BUG: Behavior unnamed "); break; } + if (has_effect(ME_DOWNED)) + wprintz(w, h_white, "On ground"); + else if (has_effect(ME_STUNNED)) + wprintz(w, h_white, "Stunned"); + else if (has_effect(ME_BEARTRAP)) + wprintz(w, h_white, "Trapped"); std::string damage_info; nc_color col; if (hp == type->hp) { @@ -220,7 +231,7 @@ void monster::draw(WINDOW *w, int plx, int ply, bool inv) nc_color monster::color_with_effects() { nc_color ret = type->color; - if (has_effect(ME_BEARTRAP) || has_effect(ME_STUNNED)) + if (has_effect(ME_BEARTRAP) || has_effect(ME_STUNNED) || has_effect(ME_DOWNED)) ret = hilite(ret); if (has_effect(ME_ONFIRE)) ret = red_background(ret); @@ -560,10 +571,12 @@ int monster::armor_bash() int monster::dodge() { + if (has_effect(ME_DOWNED)) + return 0; int ret = type->sk_dodge; if (has_effect(ME_BEARTRAP)) ret /= 2; - if (moves <= 0 - type->speed) + if (moves <= 0 - 100 - type->speed) ret = rng(0, ret); return ret; } @@ -583,6 +596,21 @@ int monster::dodge_roll() return dice(numdice, 10); } +int monster::fall_damage() +{ + if (has_flag(MF_FLIES)) + return 0; + switch (type->size) { + case MS_TINY: return rng(0, 4); break; + case MS_SMALL: return rng(0, 6); break; + case MS_MEDIUM: return dice(2, 4); break; + case MS_LARGE: return dice(2, 6); break; + case MS_HUGE: return dice(3, 5); break; + } + + return 0; +} + void monster::die(game *g) { // Drop goodies @@ -692,6 +720,12 @@ void monster::die(game *g) void monster::add_effect(monster_effect_type effect, int duration) { + for (int i = 0; i < effects.size(); i++) { + if (effects[i].type == effect) { + effects[i].duration += duration; + return; + } + } effects.push_back(monster_effect(effect, duration)); } diff --git a/monster.h b/monster.h index ee092ed622..62d7c4f8b5 100644 --- a/monster.h +++ b/monster.h @@ -17,6 +17,7 @@ ME_BEARTRAP, // Stuck in beartrap ME_POISONED, // Slowed, takes damage ME_ONFIRE, // Lit aflame ME_STUNNED, // Stumbling briefly +ME_DOWNED, // Knocked down ME_BLIND, // Can't use sight ME_DEAF, // Can't use hearing ME_TARGETED, // Targeting locked on--for robots that shoot guns @@ -88,9 +89,10 @@ class monster { point scent_move(game *g); point sound_move(game *g); - void hit_player(game *g, player &p); + void hit_player(game *g, player &p, bool can_grab = true); void move_to(game *g, int x, int y); void stumble(game *g, bool moved); + void knock_back_from(game *g, int posx, int posy); // Combat bool is_fleeing(player &u); // True if we're fleeing @@ -106,6 +108,7 @@ class monster { int armor_bash(); // Natural armor, plus any worn armor int dodge(); // Natural dodge, or 0 if we're occupied int dodge_roll(); // For the purposes of comparing to player::hit_roll() + int fall_damage(); // How much a fall hurts us void die(game *g); // Other diff --git a/mtype.h b/mtype.h index 5ace36d1cd..da4686c279 100644 --- a/mtype.h +++ b/mtype.h @@ -43,10 +43,11 @@ mon_graboid, mon_worm, mon_halfworm, mon_zombie, mon_zombie_shrieker, mon_zombie_spitter, mon_zombie_electric, mon_zombie_fast, mon_zombie_brute, mon_zombie_hulk, mon_zombie_fungus, mon_boomer, mon_boomer_fungus, mon_skeleton, mon_zombie_necro, - mon_zombie_scientist, mon_zombie_soldier, + mon_zombie_scientist, mon_zombie_soldier, mon_zombie_grabber, + mon_zombie_master, // Triffids mon_triffid, mon_triffid_young, mon_triffid_queen, mon_creeper_hub, - mon_creeper_vine, mon_biollante, mon_triffid_heart, + mon_creeper_vine, mon_biollante, mon_vinebeast, mon_triffid_heart, // Fungaloids mon_fungaloid, mon_fungaloid_dormant, mon_fungaloid_young, mon_spore, mon_fungaloid_queen, mon_fungal_wall, @@ -125,6 +126,7 @@ MF_STUMBLES, // Stumbles in its movement MF_WARM, // Warm blooded MF_NOHEAD, // Headshots not allowed! MF_HARDTOSHOOT, // Some shots are actually misses +MF_GRABS, // Its attacks may grab us! MF_BASHES, // Bashes down doors MF_DESTROYS, // Bashes down walls and more MF_POISON, // Poisonous to eat diff --git a/mtypedef.cpp b/mtypedef.cpp index df3b7a231d..105faa1fae 100644 --- a/mtypedef.cpp +++ b/mtypedef.cpp @@ -326,6 +326,7 @@ eyes. As you look at it, you're gripped by a\n\ feeling of dread and terror." ); FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_STUMBLES, MF_WARM, MF_BASHES, MF_POISON); +ANGER(MTRIG_HURT, MTRIG_PLAYER_WEAK); mon("zombie scientist",species_zombie, 'Z',c_ltgray, MS_MEDIUM, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq @@ -349,6 +350,28 @@ most zombies." ); FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_WARM, MF_BASHES, MF_POISON); +mon("grabber zombie", species_zombie, 'Z',c_green, MS_MEDIUM, FLESH, +// frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq + 6, 8,100,100, 70, 10, 1, 2, 0, 4, 0, 0, 40, 80, 0, + &mdeath::normal, &mattack::none, "\ +This zombie seems to have slightly longer\n\ +than ordinary arms, and constantly gropes\n\ +at its surroundings as it moves." +); +FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_STUMBLES, MF_WARM, MF_BASHES, MF_POISON, + MF_GRABS); + +mon("master zombie", species_zombie, 'M',c_yellow, MS_MEDIUM, FLESH, +// frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq + 1, 16, 5,100, 90, 4, 1, 3, 0, 4, 0, 0, 60,120, 3, + &mdeath::normal, &mattack::upgrade, "\ +This zombie seems to have a cloud of black\n\ +dust surrounding it. It also seems to have\n\ +a better grasp of movement than most..." +); +FLAGS(MF_SEES, MF_HEARS, MF_SMELLS, MF_WARM, MF_BASHES, MF_POISON); +ANGER(MTRIG_HURT, MTRIG_PLAYER_WEAK); + // PLANTS & FUNGI mon("triffid", species_plant, 'F', c_ltgreen, MS_MEDIUM, VEGGY, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq @@ -411,6 +434,18 @@ and pulsate ominously." ); FLAGS(MF_NOHEAD, MF_IMMOBILE); +mon("vinebeast",species_plant, 'V', c_red, MS_LARGE, VEGGY, +// frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq + 10, 14, 60, 40, 80, 15, 2, 4, 2, 4, 10, 0, 0,100, 0, + &mdeath::normal, &mattack::none, "\ +This appears to be a mass of vines, moving\n\ +with surprising speed. It is so thick and\n\ +tangled that you cannot see what lies in\n\ +the middle." +); +FLAGS(MF_HEARS, MF_GOODHEARING, MF_NOHEAD, MF_HARDTOSHOOT, MF_GRABS, + MF_SWIMS, MF_PLASTIC); + mon("triffid heart",species_plant, 'T', c_red, MS_HUGE, VEGGY, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq 0, 45,100,100,100, 0, 0, 0, 0, 0, 14, 4, 0,300, 5, @@ -708,7 +743,7 @@ ANGER(MTRIG_PLAYER_WEAK, MTRIG_PLAYER_CLOSE, MTRIG_HURT); // UNEARTHED HORRORS mon("dark wyrm",species_none, 'S', c_blue, MS_LARGE, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 1, 20,100,100, 90, 8, 2, 6, 4, 4, 6, 0, 0, 80, 0, + 1, 20,100,100,100, 8, 2, 6, 4, 4, 6, 0, 0,120, 0, &mdeath::normal, &mattack::none, "\ A huge, black worm, its flesh glistening\n\ with an acidic, blue slime. It has a gaping\n\ @@ -840,7 +875,7 @@ A shapeless blob the size of a cow. It\n\ oozes slowly across the ground, small\n\ chunks falling off of its sides." ); -FLAGS(MF_SMELLS, MF_HEARS, MF_PLASTIC); +FLAGS(MF_SMELLS, MF_HEARS, MF_PLASTIC, MF_NOHEAD); mon("flaming eye",species_nether, 'E', c_red, MS_MEDIUM, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq @@ -850,7 +885,7 @@ An eyeball the size of an easy chair and\n\ covered in rolling blue flames. It floats\n\ through the air." ); -FLAGS(MF_SEES, MF_WARM, MF_FLIES, MF_FIREY); +FLAGS(MF_SEES, MF_WARM, MF_FLIES, MF_FIREY, MF_NOHEAD); mon("kreck", species_nether, 'h', c_ltred, MS_SMALL, FLESH, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq @@ -889,18 +924,18 @@ FLAGS(MF_SEES, MF_SMELLS, MF_HEARS, MF_WARM, MF_BASHES, MF_ANIMAL, MF_FUR); // ROBOTS mon("eyebot", species_robot, 'r', c_ltblue, MS_SMALL, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 20, 2, 0,100,120, 0, 0, 0, 0, 3, 0, 10, 70, 20, 30, + 20, 2, 0,100,120, 0, 0, 0, 0, 3, 10, 10, 70, 20, 30, &mdeath::normal, &mattack::photograph, "\ A roughly spherical robot that hovers about\n\ five feet of the ground. Its front side is\n\ dominated by a huge eye and a flash bulb.\n\ Frequently used for reconaissance." ); -FLAGS(MF_SEES, MF_FLIES, MF_ELECTRONIC); +FLAGS(MF_SEES, MF_FLIES, MF_ELECTRONIC, MF_NOHEAD); mon("manhack", species_robot, 'r', c_green, MS_TINY, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 18, 7,100,100,130, 12, 1, 1, 8, 2, 0, 0, 10, 5, 0, + 18, 7,100,100,130, 12, 1, 1, 8, 4, 0, 6, 10, 15, 0, &mdeath::normal, &mattack::none, "\ A fist-sized robot that flies swiftly through\n\ the air. It's covered with whirring blades\n\ @@ -910,7 +945,7 @@ FLAGS(MF_SEES, MF_FLIES, MF_NOHEAD, MF_ELECTRONIC, MF_HIT_AND_RUN); mon("skitterbot",species_robot, 'r', c_ltred, MS_SMALL, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 10, 13,100,100,105, 0, 0, 0, 0, 0, 0, 12, 60, 40, 5, + 10, 13,100,100,105, 0, 0, 0, 0, 0, 12, 12, 60, 40, 5, &mdeath::normal, &mattack::tazer, "\ A robot with an insectoid design, about\n\ the size of a small dog. It skitters\n\ @@ -921,7 +956,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_GOODHEARING, MF_ELECTRONIC); mon("secubot", species_robot, 'R', c_dkgray, MS_SMALL, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 7, 19,100,100, 70, 0, 0, 0, 0, 0, 0, 14, 80, 80, 2, + 7, 19,100,100, 70, 0, 0, 0, 0, 0, 14, 14, 80, 80, 2, &mdeath::explode, &mattack::smg, "\ A boxy robot about four feet high. It moves\n\ slowly on a set of treads, and is armed with\n\ @@ -932,7 +967,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_BASHES, MF_ATTACKMON, MF_ELECTRONIC); mon("copbot", species_robot, 'R', c_dkgray, MS_MEDIUM, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 0, 12,100, 40,100, 4, 3, 2, 0, 8, 0, 8, 80, 80, 3, + 0, 12,100, 40,100, 4, 3, 2, 0, 8, 12, 8, 80, 80, 3, &mdeath::normal, &mattack::copbot, "\ A blue-painted robot that moves quickly on a\n\ set of three omniwheels. It has a nightstick\n\ @@ -942,7 +977,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_BASHES, MF_ATTACKMON, MF_ELECTRONIC); mon("molebot", species_robot, 'R', c_brown, MS_MEDIUM, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 2, 17,100,100, 40, 13, 1, 4, 10, 0, 0, 14, 82, 80, 0, + 2, 17,100,100, 40, 13, 1, 4, 10, 0, 14, 14, 82, 80, 0, &mdeath::normal, &mattack::none, "\ A snake-shaped robot that tunnels through the\n\ ground slowly. When it emerges from the\n\ @@ -953,7 +988,7 @@ FLAGS(MF_HEARS, MF_GOODHEARING, MF_DIGS, MF_ELECTRONIC); mon("tripod robot",species_robot, 'R', c_white, MS_LARGE, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 5, 26,100,100, 90, 15, 2, 4, 7, 0, 4, 8, 82, 80, 10, + 5, 26,100,100, 90, 15, 2, 4, 7, 0, 12, 8, 82, 80, 10, &mdeath::normal, &mattack::flamethrower, "\ A 8-foot-tall robot that walks on three long\n\ legs. It has a pair of spiked tentacles, as\n\ @@ -963,7 +998,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_GOODHEARING, MF_BASHES, MF_ELECTRONIC); mon("chicken walker",species_robot, 'R',c_red, MS_LARGE, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 3, 32,100,100,115, 0, 0, 0, 0, 0, 8, 14, 85, 90, 5, + 3, 32,100,100,115, 0, 0, 0, 0, 0, 18, 14, 85, 90, 5, &mdeath::explode, &mattack::smg, "\ A 10-foot-tall, heavily-armored robot that\n\ walks on a pair of legs with the knees\n\ @@ -974,7 +1009,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_BASHES, MF_ELECTRONIC); mon("tankbot", species_robot, 'R', c_blue, MS_HUGE, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 1, 52,100,100,100, 0, 0, 0, 0, 0, 12, 20, 92,240, 4, + 1, 52,100,100,100, 0, 0, 0, 0, 0, 22, 20, 92,240, 4, &mdeath::normal, &mattack::multi_robot, "\ This fearsome robot is essentially an\n\ autonomous tank. It moves surprisingly fast\n\ @@ -986,7 +1021,7 @@ FLAGS(MF_SEES, MF_HEARS, MF_GOODHEARING, MF_NOHEAD, MF_BASHES, MF_DESTROYS, mon("turret", species_robot, 't', c_ltgray, MS_SMALL, STEEL, // frq dif agr mor spd msk mdi m## cut dge bsh cut itm HP special freq - 0, 14,100,100,100, 0, 0, 0, 0, 0, 4, 16, 88, 30, 1, + 0, 14,100,100,100, 0, 0, 0, 0, 0, 14, 16, 88, 30, 1, &mdeath::explode, &mattack::smg, "\ A small, round turret which extends from\n\ the floor. Two SMG barrels swivel 360\n\ diff --git a/mutation.cpp b/mutation.cpp index 7d1cbf02be..147801d35c 100644 --- a/mutation.cpp +++ b/mutation.cpp @@ -7,9 +7,9 @@ void mutation_effect(game *g, player &p, pl_flag mut); void player::mutate(game *g) { - bool force_bad = one_in(2); // 50% chance! - if (has_trait(PF_ROBUST) && force_bad && one_in(2)) - force_bad = false; // 25% chance! + bool force_bad = one_in(3); // 33% chance! + if (has_trait(PF_ROBUST) && force_bad && one_in(3)) + force_bad = false; // 11% chance! // First, see if we should ugrade/extend an existing mutation std::vector upgrades; @@ -97,6 +97,7 @@ void player::mutate_towards(game *g, pl_flag mut) remove_child_flag(g, mut); return; } + bool has_prereqs = false; std::vector prereq = g->mutation_data[mut].prereqs; std::vector cancel = g->mutation_data[mut].cancels; @@ -113,13 +114,11 @@ void player::mutate_towards(game *g, pl_flag mut) return; } - for (int i = 0; i < prereq.size(); i++) { - if (has_trait(prereq[i])) { - prereq.erase(prereq.begin() + i); - i--; - } + for (int i = 0; !has_prereqs && i < prereq.size(); i++) { + if (has_trait(prereq[i])) + has_prereqs = true; } - if (!prereq.empty()) { + if (!has_prereqs && !prereq.empty()) { pl_flag devel = prereq[ rng(0, prereq.size() - 1) ]; mutate_towards(g, devel); return; diff --git a/newcharacter.cpp b/newcharacter.cpp index 9e1a73cb26..3c5d531ab7 100644 --- a/newcharacter.cpp +++ b/newcharacter.cpp @@ -75,7 +75,7 @@ bool player::create(game *g, character_type type, std::string tempname) rn = random_bad_trait(); tries++; } while ((has_trait(rn) || - num_btraits - traits[rn].points > MAX_TRAIT_POINTS) && tries < 5); + num_btraits - traits[rn].points > MAX_TRAIT_POINTS) && tries < 5); if (tries < 5) { toggle_trait(rn); points -= traits[rn].points; @@ -197,8 +197,32 @@ bool player::create(game *g, character_type type, std::string tempname) End of cheatery */ } + + if (has_trait(PF_MARTIAL_ARTS)) { + itype_id ma_type; + do { + int choice = menu("Pick your style:", + "Karate", "Judo", "Aikido", "Tai Chi", "Taekwando", NULL); + if (choice == 1) + ma_type = itm_style_karate; + if (choice == 2) + ma_type = itm_style_judo; + if (choice == 3) + ma_type = itm_style_aikido; + if (choice == 4) + ma_type = itm_style_tai_chi; + if (choice == 5) + ma_type = itm_style_taekwando; + item tmpitem = item(g->itypes[ma_type], 0); + full_screen_popup(tmpitem.info(true).c_str()); + } while (!query_yn("Use this style?")); + styles.push_back(ma_type); + } ret_null = item(g->itypes[0], 0); - weapon = item(g->itypes[0], 0); + if (!styles.empty()) + weapon = item(g->itypes[ styles[0] ], 0); + else + weapon = item(g->itypes[0], 0); // Nice to start out less than naked. item tmp(g->itypes[itm_jeans], 0, 'a'); worn.push_back(tmp); @@ -217,7 +241,7 @@ End of cheatery */ inv.push_back(tmp); } // make sure we have no mutations -for (int i = 0; i < PF_MAX2; i++) + for (int i = 0; i < PF_MAX2; i++) my_mutations[i] = false; return true; } diff --git a/npc.cpp b/npc.cpp index c015a4131f..076cef1dcd 100644 --- a/npc.cpp +++ b/npc.cpp @@ -43,9 +43,11 @@ npc::npc() per_max = 0; my_fac = NULL; marked_for_death = false; + dead = false; moves = 100; mission = NPC_MISSION_NULL; myclass = NC_NONE; + patience = 0; } npc::npc(const npc &rhs) @@ -91,8 +93,11 @@ npc& npc::operator= (npc &rhs) posy = rhs.posy; chatbin = rhs.chatbin; myclass = rhs.myclass; + patience = rhs.patience; + male = rhs.male; weapon = rhs.weapon; + ret_null = rhs.ret_null; inv = rhs.inv; worn.clear(); for (int i = 0; i < rhs.worn.size(); i++) @@ -111,7 +116,15 @@ npc& npc::operator= (npc &rhs) hp_max[i] = rhs.hp_max[i]; } + for (int i = 0; i < num_skill_types; i++) + sklevel[i] = rhs.sklevel[i]; + + styles.clear(); + for (int i = 0; i < rhs.styles.size(); i++) + styles.push_back(rhs.styles[i]); + marked_for_death = rhs.marked_for_death; + dead = rhs.dead; return *this; } @@ -149,8 +162,10 @@ npc& npc::operator= (const npc &rhs) posy = rhs.posy; chatbin = rhs.chatbin; myclass = rhs.myclass; + patience = rhs.patience; weapon = rhs.weapon; + ret_null = rhs.ret_null; inv = rhs.inv; worn.clear(); for (int i = 0; i < rhs.worn.size(); i++) @@ -169,7 +184,15 @@ npc& npc::operator= (const npc &rhs) hp_max[i] = rhs.hp_max[i]; } + for (int i = 0; i < num_skill_types; i++) + sklevel[i] = rhs.sklevel[i]; + + styles.clear(); + for (int i = 0; i < rhs.styles.size(); i++) + styles.push_back(rhs.styles[i]); + marked_for_death = rhs.marked_for_death; + dead = rhs.dead; return *this; } @@ -177,13 +200,15 @@ npc& npc::operator= (const npc &rhs) std::string npc::save_info() { std::stringstream dump; - dump << id << " " << posx << " " << posy << " " << str_cur << " " << str_max << - " " << dex_cur << " " << dex_max << " " << int_cur << " " << int_max << - " " << per_cur << " " << per_max << " " << hunger << " " << thirst << - " " << fatigue << " " << stim << " " << pain << " " << pkill << " " << - radiation << " " << cash << " " << recoil << " " << scent << " " << - moves << " " << underwater << " " << can_dodge << " " << oxygen << - " " << (marked_for_death ? "1" : "0") << " " << myclass << " "; +// The " || " is what tells npc::load_info() that it's down reading the name + dump << id << " " << name << " || " << posx << " " << posy << " " << str_cur << + " " << str_max << " " << dex_cur << " " << dex_max << " " << int_cur << + " " << int_max << " " << per_cur << " " << per_max << " " << hunger << + " " << thirst << " " << fatigue << " " << stim << " " << pain << " " << + pkill << " " << radiation << " " << cash << " " << recoil << " " << + scent << " " << moves << " " << underwater << " " << dodges_left << + " " << oxygen << " " << (marked_for_death ? "1" : "0") << " " << + (dead ? "1" : "0") << " " << myclass << " " << patience << " "; for (int i = 0; i < PF_MAX2; i++) dump << my_traits[i] << " "; @@ -192,6 +217,10 @@ std::string npc::save_info() for (int i = 0; i < num_skill_types; i++) dump << int(sklevel[i]) << " " << skexercise[i] << " "; + dump << styles.size() << " "; + for (int i = 0; i < styles.size(); i++) + dump << int(styles[i]) << " "; + dump << illness.size() << " "; for (int i = 0; i < illness.size(); i++) dump << int(illness[i].type) << " " << illness[i].duration << " "; @@ -212,46 +241,62 @@ std::string npc::save_info() int(personality.altruism) << " " << wandx << " " << wandy << " " << wandf << " " << omx << " " << omy << " " << omz << " " << mapx << " " << mapy << " " << plx << " " << ply << " " << goalx << " " << - goaly << " " << int(mission) << " " << int(op_of_u.trust) << " " << - int(op_of_u.value) << " " << int(op_of_u.fear) << " " << - int(op_of_u.anger) << " " << int(op_of_u.owed) << " " << int(flags) << - " "; + goaly << " " << int(mission) << " " << int(flags) << " "; if (my_fac == NULL) dump << -1; else dump << my_fac->id; - dump << " " << std::endl; + dump << " " << attitude << " " << " " << op_of_u.save_info() << " " << + chatbin.save_info(); + +// Inventory size, plus armor size, plus 1 for the weapon + dump << std::endl << inv.num_items() + worn.size() + 1 << std::endl; for (int i = 0; i < inv.size(); i++) { - dump << "I " << inv[i].save_info() << std::endl; - for (int j = 0; j < inv[i].contents.size(); j++) - dump << "C " << inv[i].contents[j].save_info() << std::endl; + for (int j = 0; j < inv.stack_at(i).size(); j++) { + dump << "I " << inv.stack_at(i)[j].save_info() << std::endl; + for (int k = 0; k < inv.stack_at(i)[j].contents.size(); k++) + dump << "C " << inv.stack_at(i)[j].contents[k].save_info() << std::endl; + } } + dump << "w " << weapon.save_info() << std::endl; for (int i = 0; i < worn.size(); i++) dump << "W " << worn[i].save_info() << std::endl; - dump << "w " << weapon.save_info() << std::endl; for (int j = 0; j < weapon.contents.size(); j++) dump << "c " << weapon.contents[j].save_info() << std::endl; return dump.str(); } -void npc::load_info(std::string data) +void npc::load_info(game *g, std::string data) { std::stringstream dump; - int deathtmp, classtmp; + std::string tmpname; + int deathtmp, deadtmp, classtmp; dump << data; + dump >> id; // Standard player stuff - dump >> id >> posx >> posy >> str_cur >> str_max >> dex_cur >> dex_max >> + do { + dump >> tmpname; + if (tmpname != "||") + name += tmpname + " "; + } while (tmpname != "||"); + name = name.substr(0, name.size() - 1); // Strip off trailing " " + dump >> posx >> posy >> str_cur >> str_max >> dex_cur >> dex_max >> int_cur >> int_max >> per_cur >> per_max >> hunger >> thirst >> fatigue >> stim >> pain >> pkill >> radiation >> cash >> recoil >> - scent >> moves >> underwater >> can_dodge >> oxygen >> deathtmp >> - classtmp; + scent >> moves >> underwater >> dodges_left >> oxygen >> deathtmp >> + deadtmp >> classtmp >> patience; if (deathtmp == 1) marked_for_death = true; else marked_for_death = false; + if (deadtmp == 1) + dead = true; + else + dead = false; + myclass = npc_class(classtmp); for (int i = 0; i < PF_MAX2; i++) @@ -262,9 +307,15 @@ void npc::load_info(std::string data) for (int i = 0; i < num_skill_types; i++) dump >> sklevel[i] >> skexercise[i]; - int numill; + int numill, numstyle; int typetmp; disease illtmp; + dump >> numstyle; + for (int i = 0; i < numstyle; i++) { + dump >> typetmp; + styles.push_back(itype_id(typetmp)); + } + dump >> numill; for (int i = 0; i < numill; i++) { dump >> typetmp >> illtmp.duration; @@ -288,21 +339,20 @@ void npc::load_info(std::string data) my_bionics.push_back(biotmp); } // Special NPC stuff - int misstmp, flagstmp, agg, bra, col, alt, tru, val, fea, ang, owe; + int misstmp, flagstmp, tmpatt, agg, bra, col, alt; dump >> agg >> bra >> col >> alt >> wandx >> wandy >> wandf >> omx >> omy >> omz >> mapx >> mapy >> plx >> ply >> goalx >> goaly >> misstmp >> - tru >> val >> fea >> ang >> owe >> flagstmp >> fac_id; + flagstmp >> fac_id >> tmpatt; personality.aggression = agg; personality.bravery = bra; personality.collector = col; personality.altruism = alt; - op_of_u.trust = tru; - op_of_u.value = val; - op_of_u.fear = fea; - op_of_u.anger = ang; - op_of_u.owed = owe; mission = npc_mission(misstmp); flags = flagstmp; + attitude = npc_attitude(tmpatt); + + op_of_u.load_info(dump); + chatbin.load_info(dump); } void npc::randomize(game *g, npc_class type) @@ -312,8 +362,8 @@ void npc::randomize(game *g, npc_class type) dex_max = dice(4, 3); int_max = dice(4, 3); per_max = dice(4, 3); - weapon.make(g->itypes[itm_null]); - ret_null.make(g->itypes[itm_null]); + ret_null = item(g->itypes[0], 0); + weapon = item(g->itypes[0], 0); inv.clear(); personality.aggression = rng(-10, 10); personality.bravery = rng(-10, 10); @@ -337,13 +387,16 @@ void npc::randomize(game *g, npc_class type) myclass = type; switch (type) { // Type of character case NC_NONE: // Untyped; no particular specialization - for (int i = 1; i < num_skill_types; i++) - sklevel[i] = dice(4, 2) - 4; + for (int i = 1; i < num_skill_types; i++) { + sklevel[i] = dice(4, 2) - rng(1, 4); + if (!one_in(3)) + sklevel[i] = 0; + } break; case NC_HACKER: for (int i = 1; i < num_skill_types; i++) - sklevel[i] = dice(2, 2) - 2; + sklevel[i] = dice(2, 2) - rng(1, 2); sklevel[sk_electronics] += rng(1, 4); sklevel[sk_computer] += rng(3, 6); str_max -= rng(0, 4); @@ -356,7 +409,7 @@ void npc::randomize(game *g, npc_class type) case NC_DOCTOR: for (int i = 1; i < num_skill_types; i++) - sklevel[i] = dice(3, 2) - 3; + sklevel[i] = dice(3, 2) - rng(1, 3); sklevel[sk_firstaid] += rng(2, 6); str_max -= rng(0, 2); int_max += rng(0, 2); @@ -373,7 +426,7 @@ void npc::randomize(game *g, npc_class type) sklevel[sk_mechanics] += rng(0, 2); sklevel[sk_electronics] += rng(0, 2); sklevel[sk_speech] += rng(0, 3); - sklevel[sk_barter] += rng(2, 4); + sklevel[sk_barter] += rng(2, 5); int_max += rng(0, 1) * rng(0, 1); per_max += rng(0, 1) * rng(0, 1); personality.collector += rng(1, 5); @@ -382,31 +435,35 @@ void npc::randomize(game *g, npc_class type) case NC_NINJA: for (int i = 1; i < num_skill_types; i++) - sklevel[i] = dice(2, 2) - 2; + sklevel[i] = dice(2, 2) - rng(1, 2); sklevel[sk_dodge] += rng(2, 4); sklevel[sk_melee] += rng(1, 4); - sklevel[sk_unarmed] += rng(3, 5); + sklevel[sk_unarmed] += rng(4, 6); sklevel[sk_throw] += rng(0, 2); str_max -= rng(0, 1); dex_max += rng(0, 2); per_max += rng(0, 2); personality.bravery += rng(0, 3); personality.collector -= rng(1, 6); + do + styles.push_back( itype_id( rng(itm_style_karate, itm_style_zui_quan) ) ); + while (one_in(2)); break; case NC_COWBOY: for (int i = 1; i < num_skill_types; i++) { - sklevel[i] = dice(3, 2) - 4; + sklevel[i] = dice(3, 2) - rng(0, 4); if (sklevel[i] < 0) sklevel[i] = 0; } - sklevel[sk_gun] += rng(0, 2); + sklevel[sk_gun] += rng(1, 3); sklevel[sk_pistol] += rng(1, 3); sklevel[sk_rifle] += rng(0, 2); int_max -= rng(0, 2); str_max += rng(0, 1); + per_max += rng(0, 2); personality.aggression += rng(0, 2); - personality.bravery += rng(1,5); + personality.bravery += rng(1, 5); break; case NC_SCIENTIST: @@ -418,6 +475,11 @@ void npc::randomize(game *g, npc_class type) sklevel[sk_computer] += rng(0, 3); sklevel[sk_electronics] += rng(0, 3); sklevel[sk_firstaid] += rng(0, 1); + switch (rng(1, 3)) { // pick a specialty + case 1: sklevel[sk_computer] += rng(2, 6); break; + case 2: sklevel[sk_electronics] += rng(2, 6); break; + case 3: sklevel[sk_firstaid] += rng(2, 6); break; + } if (one_in(4)) flags |= mfb(NF_TECHNOPHILE); if (one_in(3)) @@ -1010,6 +1072,7 @@ std::vector starting_inv(npc *me, npc_class type, game *g) if (ret[i].type->id == g->mapitems[mi_trader_avoid][j]) { ret.erase(ret.begin() + i); i--; + j = 0; } } } @@ -1061,6 +1124,10 @@ skill npc::best_skill() void npc::starting_weapon(game *g) { + if (!styles.empty()) { + weapon.make(g->itypes[styles[rng(0, styles.size() - 1)]]); + return; + } skill best = best_skill(); int index; switch (best) { @@ -1152,7 +1219,26 @@ bool npc::wear_if_wanted(item it) bool npc::wield(game *g, int index) { - if (index < 0 || index >= inv.size()) { + if (index < 0) { // Wielding a style + index = 0 - index - 1; + if (index >= styles.size()) { + debugmsg("npc::wield(%d) [styles.size() = %d]", index, styles.size()); + return false; + } + weapon.make( g->itypes[styles[index]] ); + if (volume_carried() + weapon.volume() <= volume_capacity()) { + i_add(remove_weapon()); + moves -= 15; + } else // No room for weapon, so we drop it + g->m.add_item(posx, posy, remove_weapon()); + moves -= 15; + int linet; + if (g->u_see(posx, posy, linet)) + g->add_msg("%s assumes a %s stance.", name.c_str(), weapon.tname().c_str()); + return true; + } + + if (index >= inv.size()) { debugmsg("npc::wield(%d) [inv.size() = %d]", index, inv.size()); return false; } @@ -1203,10 +1289,10 @@ void npc::form_opinion(player *u) if (weapon.is_gun()) op_of_u.fear += 2; else - op_of_u.fear += 4; + op_of_u.fear += 6; } else if (u->weapon.type->melee_dam >= 12 || u->weapon.type->melee_cut >= 12) - op_of_u.fear += 1; - else if (u->weapon.type->id == 0) // Unarmed + op_of_u.fear += 2; + else if (u->unarmed_attack()) // Unarmed op_of_u.fear -= 3; if (u->str_max >= 16) @@ -1282,6 +1368,26 @@ void npc::form_opinion(player *u) attitude = NPCATT_FLEE; } +talk_topic npc::pick_talk_topic(player *u) +{ + form_opinion(u); + if (personality.aggression > 0) { + if (op_of_u.fear * 2 < personality.bravery && personality.altruism < 0) + return TALK_MUG; + if (personality.aggression + personality.bravery - op_of_u.fear > 0) + return TALK_STRANGER_AGGRESSIVE; + } + if (op_of_u.fear * 2 > personality.altruism + personality.bravery) + return TALK_STRANGER_SCARED; + if (op_of_u.fear * 2 > personality.bravery + op_of_u.trust) + return TALK_STRANGER_WARY; + if (op_of_u.trust - op_of_u.fear + + (personality.bravery + personality.altruism) / 2 > 0) + return TALK_STRANGER_FRIENDLY; + + return TALK_STRANGER_NEUTRAL; +} + int npc::player_danger(player *u) { int ret = 0; @@ -1332,7 +1438,7 @@ bool npc::turned_hostile() int npc::hostile_anger_level() { - return (20 + op_of_u.fear - personality.aggression); + return (10 + op_of_u.fear - personality.aggression); } void npc::make_angry() @@ -1364,6 +1470,36 @@ int npc::assigned_missions_value(game *g) return ret; } +std::vector npc::skills_offered_to(player *p) +{ + std::vector ret; + if (p == NULL) + return ret; + for (int i = 0; i < num_skill_types; i++) { + if (sklevel[i] > p->sklevel[i]) + ret.push_back( skill(i) ); + } + return ret; +} + +std::vector npc::styles_offered_to(player *p) +{ + std::vector ret; + if (p == NULL) + return ret; + for (int i = 0; i < styles.size(); i++) { + bool found = false; + for (int j = 0; j < p->styles.size() && !found; j++) { + if (p->styles[j] == styles[i]) + found = true; + } + if (!found) + ret.push_back( styles[i] ); + } + return ret; +} + + int npc::minutes_to_u(game *g) { int ret = abs(mapx - g->levx); @@ -1614,7 +1750,8 @@ bool npc::took_painkiller() bool npc::is_friend() { - if (attitude == NPCATT_FOLLOW || attitude == NPCATT_DEFEND) + if (attitude == NPCATT_FOLLOW || attitude == NPCATT_DEFEND || + attitude == NPCATT_LEAD) return true; return false; } @@ -1633,6 +1770,11 @@ bool npc::is_following() } } +bool npc::is_leader() +{ + return (attitude == NPCATT_LEAD); +} + bool npc::is_enemy() { if (attitude == NPCATT_KILL || attitude == NPCATT_MUG || @@ -1935,6 +2077,9 @@ void npc::shift(int sx, int sy) void npc::die(game *g, bool your_fault) { + if (dead) + return; + dead = true; int j; if (g->u_see(posx, posy, j)) g->add_msg("%s dies!", name.c_str()); @@ -1945,11 +2090,6 @@ void npc::die(game *g, bool your_fault) g->u.add_morale(MORALE_KILLED_INNOCENT, -100); } - for (int i = 0; i < g->active_missions.size(); i++) { - if (g->active_missions[i].npc_id == id) - g->fail_mission( g->active_missions[i].uid ); - } - item my_body; my_body.make_corpse(g->itypes[itm_corpse], g->mtypes[mon_null], g->turn); my_body.name = name; @@ -1960,6 +2100,12 @@ void npc::die(game *g, bool your_fault) g->m.add_item(posx, posy, worn[i]); if (weapon.type->id != itm_null) g->m.add_item(posx, posy, weapon); + + for (int i = 0; i < g->active_missions.size(); i++) { + if (g->active_missions[i].npc_id == id) + g->fail_mission( g->active_missions[i].uid ); + } + } std::string random_first_name(bool male) diff --git a/npc.h b/npc.h index 2d0f3d07c2..dab37078ba 100644 --- a/npc.h +++ b/npc.h @@ -7,6 +7,7 @@ #include "faction.h" #include #include +#include #define NPC_LOW_VALUE 5 #define NPC_HI_VALUE 8 @@ -29,15 +30,18 @@ void parse_tags(std::string &phrase, player *u, npc *me); * Flee: Trust low, fear mid->high, need low */ +// Attitude is how we feel about the player, what we do around them enum npc_attitude { NPCATT_NULL = 0, // Don't care/ignoring player NPCATT_TALK, // Move to and talk to player NPCATT_TRADE, // Move to and trade with player NPCATT_FOLLOW, // Follow the player NPCATT_FOLLOW_RUN, // Follow the player, don't shoot monsters + NPCATT_LEAD, // Lead the player, wait for them if they're behind NPCATT_WAIT, // Waiting for the player NPCATT_DEFEND, // Kill monsters that threaten the player NPCATT_MUG, // Mug the player + NPCATT_WAIT_FOR_LEAVE, // Attack the player if our patience runs out NPCATT_KILL, // Kill the player NPCATT_FLEE, // Get away from the player NPCATT_SLAVE, // Following the player under duress @@ -103,6 +107,31 @@ enum npc_flag { NF_MAX }; +enum npc_favor_type { + FAVOR_NULL, + FAVOR_GENERAL, // We owe you... a favor? + FAVOR_CASH, // We owe cash (or goods of equivalent value) + FAVOR_ITEM, // We owe a specific item + FAVOR_TRAINING,// We owe skill or style training + NUM_FAVOR_TYPES +}; + +struct npc_favor +{ + npc_favor_type type; + int value; + itype_id item_id; + skill skill_id; + + npc_favor() { + type = FAVOR_NULL; + value = 0; + item_id = itm_null; + skill_id = sk_null; + }; + +}; + struct npc_personality { // All values should be in the -10 to 10 range. signed char aggression; @@ -117,18 +146,26 @@ struct npc_personality { }; }; -struct npc_opinion { - signed char trust; - signed char fear; - signed char value; - signed char anger; +struct npc_opinion +{ + int trust; + int fear; + int value; + int anger; int owed; + std::vector favors; + + int total_owed() { + int ret = owed; + return ret; + } + npc_opinion() { trust = 0; fear = 0; value = 0; anger = 0; - owed = 0; + owed = 0; }; npc_opinion(signed char T, signed char F, signed char V, signed char A, int O): trust (T), fear (F), value (V), anger(A), owed (O) { }; @@ -140,6 +177,9 @@ struct npc_opinion { value = copy.value; anger = copy.anger; owed = copy.owed; + favors.clear(); + for (int i = 0; i < copy.favors.size(); i++) + favors.push_back( copy.favors[i] ); }; npc_opinion& operator+= (npc_opinion &rhs) @@ -152,10 +192,48 @@ struct npc_opinion { return *this; }; +/* + npc_opinion& operator+= (npc_opinion rhs) + { + trust += rhs.trust; + fear += rhs.fear; + value += rhs.value; + anger += rhs.anger; + owed += rhs.owed; + return *this; + }; +*/ + npc_opinion& operator+ (npc_opinion &rhs) { return (npc_opinion(*this) += rhs); }; + + std::string save_info() + { + std::stringstream ret; + ret << trust << " " << fear << " " << value << " " << anger << " " << owed << + " " << favors.size(); + for (int i = 0; i < favors.size(); i++) + ret << " " << int(favors[i].type) << " " << favors[i].value << " " << + favors[i].item_id << " " << favors[i].skill_id; + return ret.str(); + } + + void load_info(std::stringstream &info) + { + int tmpsize; + info >> trust >> fear >> value >> anger >> owed >> tmpsize; + for (int i = 0; i < tmpsize; i++) { + int tmptype, tmpitem, tmpskill; + npc_favor tmpfavor; + info >> tmptype >> tmpfavor.value >> tmpitem >> tmpskill; + tmpfavor.type = npc_favor_type(tmptype); + tmpfavor.item_id = itype_id(tmpitem); + tmpfavor.skill_id = skill(tmpskill); + favors.push_back(tmpfavor); + } + } }; enum combat_engagement { @@ -206,18 +284,38 @@ enum talk_topic { TALK_GIVE_EQUIPMENT, TALK_DENY_EQUIPMENT, - TALK_SUGGEST_FOLLOW, + TALK_TRAIN, + TALK_TRAIN_START, + TALK_TRAIN_FORCE, + TALK_SUGGEST_FOLLOW, TALK_AGREE_FOLLOW, TALK_DENY_FOLLOW, TALK_SHOPKEEP, - TALK_FRIEND, + TALK_LEADER, + TALK_LEAVE, + TALK_PLAYER_LEADS, + TALK_LEADER_STAYS, + TALK_HOW_MUCH_FURTHER, + TALK_FRIEND, TALK_COMBAT_COMMANDS, TALK_COMBAT_ENGAGEMENT, + TALK_STRANGER_NEUTRAL, + TALK_STRANGER_WARY, + TALK_STRANGER_SCARED, + TALK_STRANGER_FRIENDLY, + TALK_STRANGER_AGGRESSIVE, + TALK_MUG, + + TALK_DESCRIBE_MISSION, + + TALK_WEAPON_DROPPED, + TALK_DEMAND_LEAVE, + TALK_SIZE_UP, TALK_LOOK_AT, TALK_OPINION, @@ -230,17 +328,48 @@ struct npc_chatbin std::vector missions; std::vector missions_assigned; int mission_selected; + int tempvalue; talk_topic first_topic; npc_chatbin() { mission_selected = -1; + tempvalue = -1; first_topic = TALK_NONE; } + + std::string save_info() + { + std::stringstream ret; + ret << first_topic << " " << mission_selected << " " << tempvalue << " " << + missions.size() << " " << missions_assigned.size(); + for (int i = 0; i < missions.size(); i++) + ret << " " << missions[i]; + for (int i = 0; i < missions_assigned.size(); i++) + ret << " " << missions_assigned[i]; + return ret.str(); + } + + void load_info(std::stringstream &info) + { + int tmpsize_miss, tmpsize_assigned, tmptopic; + info >> tmptopic >> mission_selected >> tempvalue >> tmpsize_miss >> + tmpsize_assigned; + first_topic = talk_topic(tmptopic); + for (int i = 0; i < tmpsize_miss; i++) { + int tmpmiss; + info >> tmpmiss; + missions.push_back(tmpmiss); + } + for (int i = 0; i < tmpsize_assigned; i++) { + int tmpmiss; + info >> tmpmiss; + missions_assigned.push_back(tmpmiss); + } + } }; std::string random_first_name(bool male); - std::string random_last_name(); class npc : public player { @@ -267,7 +396,7 @@ class npc : public player { // Save & load - virtual void load_info(std::string data);// Overloaded from player::load_info() + virtual void load_info(game *g, std::string data);// Overloaded from player virtual std::string save_info(); @@ -284,19 +413,22 @@ class npc : public player { bool fac_has_value(faction_value value); bool fac_has_job(faction_job job); - // Interaction with the player void form_opinion(player *u); + talk_topic pick_talk_topic(player *u); int player_danger(player *u); // Comparable to monsters bool turned_hostile(); // True if our anger is at least equal to... int hostile_anger_level(); // ... this value! void make_angry(); // Called if the player attacks us bool wants_to_travel_with(player *p); int assigned_missions_value(game *g); + std::vector skills_offered_to(player *p); // Skills that're higher + std::vector styles_offered_to(player *p); // Martial Arts // State checks bool is_enemy(); // We want to kill/mug/etc the player bool is_following(); // Traveling w/ player (whether as a friend or a slave) bool is_friend(); // Allies with the player + bool is_leader(); // Leading the player bool is_defending(); // Putting the player's safety ahead of ours // What happens when the player makes a request void told_to_help(game *g); @@ -398,7 +530,8 @@ class npc : public player { // Movement on the overmap scale bool has_destination(); // Do we have a long-term destination? void set_destination(game *g); // Pick a place to go - void go_to_destination(game *g); + void go_to_destination(game *g); // Move there; on the micro scale + void reach_destination(game *g); // We made it! // The preceding are in npcmove.cpp @@ -432,8 +565,10 @@ class npc : public player { npc_personality personality; npc_opinion op_of_u; npc_chatbin chatbin; + int patience; // Used when we expect the player to leave the area npc_combat_rules combat_rules; bool marked_for_death; // If true, we die as soon as we respawn! + bool dead; // If true, we need to be cleaned up std::vector needs; unsigned flags : NF_MAX; }; diff --git a/npcmove.cpp b/npcmove.cpp index b9b318132e..723ca1ba4a 100644 --- a/npcmove.cpp +++ b/npcmove.cpp @@ -546,13 +546,39 @@ npc_action npc::address_player(game *g) say(g, "Don't move a muscle..."); return npc_mug_player; } + + if (attitude == NPCATT_WAIT_FOR_LEAVE) { + patience--; + if (patience <= 0) { + patience = 0; + attitude = NPCATT_KILL; + return method_of_attack(g, TARGET_PLAYER, player_danger( &(g->u) )); + } + return npc_undecided; + } if (attitude == NPCATT_FLEE) return npc_flee; + if (attitude == NPCATT_LEAD) { + if (rl_dist(posx, posy, g->u.posx, g->u.posy) >= 12 || + !g->sees_u(posx, posy, linet)) { + int intense = disease_intensity(DI_CATCH_UP); + if (intense < 10) { + say(g, ""); + add_disease(DI_CATCH_UP, 5, g, 1, 15); + return npc_pause; + } else if (intense == 10) { + say(g, ""); + add_disease(DI_CATCH_UP, 5, g, 1, 15); + return npc_pause; + } else + return npc_goto_destination; + } else + return npc_goto_destination; + } return npc_undecided; } - npc_action npc::long_term_goal_action(game *g) { @@ -828,6 +854,10 @@ bool npc::can_move_to(game *g, int x, int y) void npc::move_to(game *g, int x, int y) { + if (has_disease(DI_DOWNED)) { + moves -= 100; + return; + } if (recoil > 0) { // Start by dropping recoil a little if (int(str_cur / 2) + sklevel[sk_gun] >= recoil) recoil = 0; @@ -836,6 +866,10 @@ void npc::move_to(game *g, int x, int y) recoil = int(recoil / 2); } } + if (has_disease(DI_STUNNED)) { + x = rng(posx - 1, posx + 1); + y = rng(posy - 1, posy + 1); + } if (rl_dist(posx, posy, x, y) > 1) { /* debugmsg("Tried to move_to more than one space! (%d, %d) to (%d, %d)", @@ -853,7 +887,7 @@ void npc::move_to(game *g, int x, int y) moves -= 100; else if (g->mon_at(x, y) != -1) { // Shouldn't happen, but it might. monster *m = &(g->z[g->mon_at(x, y)]); - debugmsg("Bumped into a monster, %d, a %s",g->mon_at(x, y),m->name().c_str()); + //debugmsg("Bumped into a monster, %d, a %s",g->mon_at(x, y),m->name().c_str()); melee_monster(g, g->mon_at(x, y)); } else if (g->u.posx == x && g->u.posy == y) { say(g, ""); @@ -1317,9 +1351,12 @@ void npc::melee_monster(game *g, int target) void npc::melee_player(game *g, player &foe) { + hit_player(g, foe); +/* int dam = 0, cut = 0; body_part hit; - if (hit_player(g, foe, hit, dam, cut)) { + + if (hit_player(g, foe)) { int side = rng(0, 1); g->add_msg("%s hits your %s with %s %s.", name.c_str(), body_part_name(hit, side).c_str(), (male ? "his" : "her"), @@ -1328,10 +1365,16 @@ void npc::melee_player(game *g, player &foe) } else g->add_msg("%s swings %s %s at you, but misses.", name.c_str(), (male ? "his" : "her"), weapname(false).c_str()); +*/ } void npc::wield_best_melee(game *g) { + if (!styles.empty()) { // Always wield a style if we have one +// TODO: More intelligent style choosing + wield(g, 0 - rng(1, styles.size())); + return; + } int best_score = 0, index = -1; for (int i = 0; i < inv.size(); i++) { int score = inv[i].melee_value(sklevel); @@ -1419,6 +1462,7 @@ void npc::alt_attack(game *g, int target) if (g->u_see(posx, posy, linet)) g->add_msg("%s throws a %s.", name.c_str(), used->tname().c_str()); g->throw_item(*this, tarx, tary, *used, trajectory); + i_remn(index); } else if (!wont_hit_friend(g, tarx, tary, index)) {// Danger of friendly fire @@ -1470,6 +1514,7 @@ void npc::alt_attack(game *g, int target) if (g->u_see(posx, posy, linet)) g->add_msg("%s throws a %s.", name.c_str(), used->tname().c_str()); g->throw_item(*this, tarx, tary, *used, trajectory); + i_remn(index); } } else { // Within this block, our chosen target is outside of our range @@ -1819,6 +1864,12 @@ bool npc::has_destination() return (goalx >= 0 && goalx < OMAPX && goaly >= 0 && goaly < OMAPY); } +void npc::reach_destination(game *g) +{ + goalx = -1; + goaly = -1; +} + void npc::set_destination(game *g) { /* TODO: Make NPCs' movement more intelligent. @@ -1870,9 +1921,10 @@ void npc::set_destination(game *g) void npc::go_to_destination(game *g) { int sx = (goalx > mapx ? 1 : -1), sy = (goaly > mapy ? 1 : -1); - if (goalx == mapx && goaly == mapy) // We're at our desired map square! + if (goalx == mapx && goaly == mapy) { // We're at our desired map square! move_pause(); - else { + reach_destination(g); + } else { if (goalx == mapx) sx = 0; if (goaly == mapy) diff --git a/npctalk.cpp b/npctalk.cpp index 5c54f73849..511cf6b9d9 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -21,17 +21,26 @@ ret.back().text = txt;\ ret.back().mission_index = index +#define SELECT_TEMP(txt, index) ret.push_back(talk_response());\ + ret.back().text = txt;\ + ret.back().tempvalue = index + #define TRIAL(tr, diff) ret.back().trial = tr;\ ret.back().difficulty = diff + #define SUCCESS(topic) ret.back().success = topic #define FAILURE(topic) ret.back().failure = topic + #define SUCCESS_OPINION(T, F, V, A, O) ret.back().opinion_success =\ npc_opinion(T, F, V, A, O) #define FAILURE_OPINION(T, F, V, A, O) ret.back().opinion_failure =\ npc_opinion(T, F, V, A, O) + #define SUCCESS_ACTION(func) ret.back().effect_success = func #define FAILURE_ACTION(func) ret.back().effect_failure = func +#define SUCCESS_MISSION(type) ret.back().miss = type + std::string dynamic_line(talk_topic topic, game *g, npc *p); std::vector gen_responses(talk_topic topic, game *g, npc *p); int topic_category(talk_topic topic); @@ -44,15 +53,25 @@ bool trade(game *g, npc *p, int cost, std::string deal); void npc::talk_to_u(game *g) { +// This is necessary so that we don't bug the player over and over if (attitude == NPCATT_TALK) attitude = NPCATT_NULL; + else if (attitude == NPCATT_FLEE) { + g->add_msg("%d is fleeing you!", name.c_str()); + return; + } else if (attitude == NPCATT_KILL) { + g->add_msg("%d is hostile!", name.c_str()); + return; + } dialogue d; d.alpha = &g->u; d.beta = this; d.topic_stack.push_back(chatbin.first_topic); - if (is_friend()) + if (is_leader()) + d.topic_stack.push_back(TALK_LEADER); + else if (is_friend()) d.topic_stack.push_back(TALK_FRIEND); int most_difficult_mission = 0; @@ -79,13 +98,12 @@ void npc::talk_to_u(game *g) } if (d.topic_stack.back() == TALK_NONE) { - g->add_msg("%s says, \"Leave me alone.\"", name.c_str()); + d.topic_stack.back() = pick_talk_topic(&(g->u)); return; } moves -= 100; decide_needs(); - d.win = newwin(25, 80, 0, 0); wborder(d.win, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, @@ -192,6 +210,8 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) return "I don't know, look for supplies and other survivors I guess."; case TALK_SHARE_EQUIPMENT: + if (p->has_disease(DI_ASKED_FOR_ITEM)) + return "You just asked me for stuff; ask later."; return "Why should I share my equipment with you?"; case TALK_GIVE_EQUIPMENT: @@ -203,6 +223,27 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) else return " !"; + case TALK_TRAIN: { + if (g->u.backlog.type == ACT_TRAIN) + return "Shall we resume?"; + std::vector trainable = p->skills_offered_to( &(g->u) ); + std::vector styles = p->styles_offered_to( &(g->u) ); + if (trainable.empty() && styles.empty()) + return "Sorry, but it doesn't seem I have anything to teach you."; + else + return "Here's what I can teach you..."; + } break; + + case TALK_TRAIN_START: + if (g->cur_om.is_safe(g->om_location().x, g->om_location().y)) + return "Alright, let's begin."; + else + return "It's not safe here. Let's get to safety first."; + break; + + case TALK_TRAIN_FORCE: + return "Alright, let's begin."; + case TALK_SUGGEST_FOLLOW: if (p->has_disease(DI_INFECTION)) return "Not until I get some antibiotics..."; @@ -216,6 +257,34 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) case TALK_DENY_FOLLOW: return "Yeah... I don't think so."; + case TALK_LEADER: + return "What is it?"; + + case TALK_LEAVE: + return "You're really leaving?"; + + case TALK_PLAYER_LEADS: + return "Alright. You can lead now."; + + case TALK_LEADER_STAYS: + return "No. I'm the leader here."; + + case TALK_HOW_MUCH_FURTHER: { + int dist = rl_dist(g->om_location(), point(p->goalx, p->goaly)); + std::stringstream response; + dist *= 100; + if (dist >= 1300) { + int miles = dist / 52; // *100, e.g. quarter mile is "25" + miles -= miles % 25; // Round to nearest quarter-mile + int fullmiles = (miles - miles % 100) / 100; // Left of the decimal point + if (fullmiles > 0) + response << fullmiles; + response << "." << miles << " miles."; + } else + response << dist << " feet."; + return response.str(); + } + case TALK_FRIEND: return "What is it?"; @@ -240,6 +309,87 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) case TALK_COMBAT_ENGAGEMENT: return "What should I do?"; + case TALK_STRANGER_NEUTRAL: + if (p->myclass == NC_TRADER) + return "Hello! Would you care to see my goods?"; + return "Hello there."; + + case TALK_STRANGER_WARY: + return "Okay, no sudden movements..."; + + case TALK_STRANGER_SCARED: + return "Keep your distance!"; + + case TALK_STRANGER_FRIENDLY: + if (p->myclass == NC_TRADER) + return "Hello! Would you care to see my goods?"; + return "Hey there, ."; + + case TALK_STRANGER_AGGRESSIVE: + if (!g->u.unarmed_attack()) + return ""; + else + return "This is my territory, ."; + + case TALK_MUG: + if (!g->u.unarmed_attack()) + return ""; + else + return ""; + + case TALK_DESCRIBE_MISSION: + switch (p->mission) { + case NPC_MISSION_RESCUE_U: + return "I'm here to save you!"; + case NPC_MISSION_SHELTER: + return "I'm holing up here for safety."; + case NPC_MISSION_SHOPKEEP: + return "I run the shop here."; + case NPC_MISSION_MISSING: + return "Well, I was lost, but you found me..."; + case NPC_MISSION_KIDNAPPED: + return "Well, I was kidnapped, but you saved me..."; + case NPC_MISSION_NULL: + switch (p->myclass) { + case NC_HACKER: + return "I'm looking for some choice systems to hack."; + case NC_DOCTOR: + return "I'm looking for wounded to help."; + case NC_TRADER: + return "I'm collecting gear and selling it."; + case NC_NINJA: + if (!p->styles.empty()) { + std::stringstream ret; + ret << "I am a wandering master of " << + g->itypes[p->styles[0]]->name.c_str() << "."; + return ret.str(); + } else + return "I am looking for a master to train my fighting techniques."; + case NC_COWBOY: + return "Just looking for some wrongs to right."; + case NC_SCIENTIST: + return "I'm looking for clues concerning these monsters' origins..."; + case NC_BOUNTY_HUNTER: + return "I'm a killer for hire."; + case NC_NONE: + return "I'm just wandering."; + default: + return "ERROR: Someone forgot to code an npc_class text."; + } // switch (p->myclass) + default: + return "ERROR: Someone forgot to code an npc_mission text."; + } // switch (p->mission) + break; + + case TALK_WEAPON_DROPPED: { + std::stringstream ret; + ret << "*drops " << (p->male ? "his" : "her") << " weapon."; + return ret.str(); + } + + case TALK_DEMAND_LEAVE: + return "Now get out of here, before I kill you."; + case TALK_SIZE_UP: { int ability = g->u.per_cur * 3 + g->u.int_cur; if (ability <= 10) @@ -290,6 +440,10 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) std::vector gen_responses(talk_topic topic, game *g, npc *p) { std::vector ret; + int selected = p->chatbin.mission_selected; + mission *miss = NULL; + if (selected != -1 && selected < p->chatbin.missions_assigned.size()) + miss = g->find_mission( p->chatbin.missions_assigned[selected] ); switch (topic) { case TALK_MISSION_LIST: @@ -435,17 +589,23 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) case TALK_MISSION_SUCCESS: RESPONSE("Glad to help. I need no payment."); SUCCESS(TALK_NONE); - SUCCESS_OPINION(p->op_of_u.owed / (OWED_VAL * 4), -1, - p->op_of_u.owed / (OWED_VAL * 2), -1, 0 - p->op_of_u.owed); + SUCCESS_OPINION(miss->value / (OWED_VAL * 4), -1, + miss->value / (OWED_VAL * 2), -1, 0 - miss->value); SUCCESS_ACTION(&talk_function::clear_mission); - RESPONSE("How about payment?"); + RESPONSE("How about some items as payment?"); SUCCESS(TALK_MISSION_REWARD); SUCCESS_ACTION(&talk_function::mission_reward); + SELECT_TEMP("Maybe you can teach me something as payment.", 0); + SUCCESS(TALK_TRAIN); + SUCCESS_ACTION(&talk_function::clear_mission); + RESPONSE("Alright, well, you owe me one."); + SUCCESS(TALK_NONE); + SUCCESS_ACTION(&talk_function::clear_mission); RESPONSE("Glad to help. I need no payment. Bye!"); SUCCESS(TALK_DONE); SUCCESS_ACTION(&talk_function::clear_mission); SUCCESS_OPINION(p->op_of_u.owed / (OWED_VAL * 4), -1, - p->op_of_u.owed / (OWED_VAL * 2), -1, 0 - p->op_of_u.owed); + p->op_of_u.owed / (OWED_VAL * 2), -1, 0 - miss->value); break; case TALK_MISSION_SUCCESS_LIE: @@ -495,46 +655,51 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) SUCCESS(TALK_DONE); break; - case TALK_SHARE_EQUIPMENT: { - int score = p->op_of_u.trust + p->op_of_u.value * 3 + - p->personality.altruism * 2; - int missions_value = p->assigned_missions_value(g); - RESPONSE("Because I'm your friend!"); - TRIAL(TALK_TRIAL_PERSUADE, 10 + score); - SUCCESS(TALK_GIVE_EQUIPMENT); - SUCCESS_ACTION(&talk_function::give_equipment); - SUCCESS_OPINION(0, 0, -1, 0, score * 300); - FAILURE(TALK_DENY_EQUIPMENT); - FAILURE_OPINION(0, 0, -1, 0, 0); - if (missions_value >= 1) { - RESPONSE("Well, I am helping you out..."); - TRIAL(TALK_TRIAL_PERSUADE, 10 + score + p->op_of_u.owed / OWED_VAL); + case TALK_SHARE_EQUIPMENT: + if (p->has_disease(DI_ASKED_FOR_ITEM)) { + RESPONSE("Okay, fine."); + SUCCESS(TALK_NONE); + } else { + int score = p->op_of_u.trust + p->op_of_u.value * 3 + + p->personality.altruism * 2; + int missions_value = p->assigned_missions_value(g); + RESPONSE("Because I'm your friend!"); + TRIAL(TALK_TRIAL_PERSUADE, 10 + score); SUCCESS(TALK_GIVE_EQUIPMENT); SUCCESS_ACTION(&talk_function::give_equipment); + SUCCESS_OPINION(0, 0, -1, 0, score * 300); FAILURE(TALK_DENY_EQUIPMENT); FAILURE_OPINION(0, 0, -1, 0, 0); + if (missions_value >= 1) { + RESPONSE("Well, I am helping you out..."); + TRIAL(TALK_TRIAL_PERSUADE, 12 + (.8 * score) + missions_value / OWED_VAL); + SUCCESS(TALK_GIVE_EQUIPMENT); + SUCCESS_ACTION(&talk_function::give_equipment); + FAILURE(TALK_DENY_EQUIPMENT); + FAILURE_OPINION(0, 0, -1, 0, 0); + } + RESPONSE("I'll give it back!"); + TRIAL(TALK_TRIAL_LIE, score * 1.5); + SUCCESS(TALK_GIVE_EQUIPMENT); + SUCCESS_ACTION(&talk_function::give_equipment); + SUCCESS_OPINION(0, 0, -1, 0, score * 300); + FAILURE(TALK_DENY_EQUIPMENT); + FAILURE_OPINION(0, -1, -1, 1, 0); + RESPONSE("Give it to me, or else!"); + TRIAL(TALK_TRIAL_INTIMIDATE, 40); + SUCCESS(TALK_GIVE_EQUIPMENT); + SUCCESS_ACTION(&talk_function::give_equipment); + SUCCESS_OPINION(-3, 2, -2, 2, + (g->u.intimidation() + p->op_of_u.fear - + p->personality.bravery - p->intimidation()) * 500); + FAILURE(TALK_DENY_EQUIPMENT); + FAILURE_OPINION(-3, 1, -3, 5, 0); + RESPONSE("Eh, never mind."); + SUCCESS(TALK_NONE); + RESPONSE("Never mind, I'll do without. Bye."); + SUCCESS(TALK_DONE); } - RESPONSE("I'll give it back!"); - TRIAL(TALK_TRIAL_LIE, score * 1.5); - SUCCESS(TALK_GIVE_EQUIPMENT); - SUCCESS_ACTION(&talk_function::give_equipment); - SUCCESS_OPINION(0, 0, -1, 0, score * 300); - FAILURE(TALK_DENY_EQUIPMENT); - FAILURE_OPINION(0, -1, -1, 1, 0); - RESPONSE("Give it to me, or else!"); - TRIAL(TALK_TRIAL_INTIMIDATE, 40); - SUCCESS(TALK_GIVE_EQUIPMENT); - SUCCESS_ACTION(&talk_function::give_equipment); - SUCCESS_OPINION(-3, 2, -2, 2, - (g->u.intimidation() + p->op_of_u.fear - - p->personality.bravery - p->intimidation()) * 500); - FAILURE(TALK_DENY_EQUIPMENT); - FAILURE_OPINION(-3, 1, -3, 5, 0); - RESPONSE("Eh, never mind."); - SUCCESS(TALK_NONE); - RESPONSE("Never mind, I'll do without. Bye."); - SUCCESS(TALK_DONE); - } break; + break; case TALK_GIVE_EQUIPMENT: RESPONSE("Thank you!"); @@ -554,6 +719,79 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) SUCCESS(TALK_DONE); break; + case TALK_TRAIN: { + if (g->u.backlog.type == ACT_TRAIN) { + std::stringstream resume; + resume << "Yes, let's resume training " << + (g->u.backlog.index > 0 ? + skill_name(g->u.backlog.index) : + g->itypes[ 0 - g->u.backlog.index ]->name); + SELECT_TEMP( resume.str(), g->u.backlog.index); + SUCCESS(TALK_TRAIN_START); + } + std::vector trainable = p->skills_offered_to( &(g->u) ); + std::vector styles = p->styles_offered_to( &(g->u) ); + if (trainable.empty() && styles.empty()) { + RESPONSE("Oh, okay."); // Nothing to learn here + SUCCESS(TALK_NONE); + } + int printed = 0; + int shift = p->chatbin.tempvalue; + bool more = trainable.size() + styles.size() - shift > 9; + for (int i = shift; i < trainable.size() && printed < 9; i++) { + shift--; + printed++; + std::stringstream skilltext; + skill trained = trainable[i]; + + skilltext << skill_name(trained) << ": " << g->u.sklevel[trained] << + " -> " << g->u.sklevel[trained] + 1 << "(cost " << + 200 * (g->u.sklevel[trained] + 1) << ")"; + SELECT_TEMP( skilltext.str(), trainable[i] ); + SUCCESS(TALK_TRAIN_START); + } + if (shift < 0) + shift = 0; + for (int i = shift; i < styles.size() && printed < 9; i++) { + printed++; + SELECT_TEMP( g->itypes[styles[i]]->name + " (cost 800)", + 0 - styles[i] ); + SUCCESS(TALK_TRAIN_START); + } + if (more) { + SELECT_TEMP("More...", shift + 9); + SUCCESS(TALK_TRAIN); + } + RESPONSE("Eh, never mind."); + SUCCESS(TALK_NONE); + } break; + + case TALK_TRAIN_START: + if (g->cur_om.is_safe(g->om_location().x, g->om_location().y)) { + RESPONSE("Sounds good."); + SUCCESS(TALK_DONE); + SUCCESS_ACTION(&talk_function::start_training); + RESPONSE("On second thought, never mind."); + SUCCESS(TALK_NONE); + } else { + RESPONSE("Okay. Lead the way."); + SUCCESS(TALK_DONE); + SUCCESS_ACTION(&talk_function::lead_to_safety); + RESPONSE("No, we'll be okay here."); + SUCCESS(TALK_TRAIN_FORCE); + RESPONSE("On second thought, never mind."); + SUCCESS(TALK_NONE); + } + break; + + case TALK_TRAIN_FORCE: + RESPONSE("Sounds good."); + SUCCESS(TALK_DONE); + SUCCESS_ACTION(&talk_function::start_training); + RESPONSE("On second thought, never mind."); + SUCCESS(TALK_NONE); + break; + case TALK_SUGGEST_FOLLOW: if (p->has_disease(DI_INFECTION)) { RESPONSE("Understood. I'll get those antibiotics."); @@ -614,11 +852,73 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) SUCCESS(TALK_NONE); break; + case TALK_LEADER: { + int persuade = p->op_of_u.fear + p->op_of_u.value + p->op_of_u.trust - + p->personality.bravery - p->personality.aggression; + if (p->has_destination()) { + RESPONSE("How much further?"); + SUCCESS(TALK_HOW_MUCH_FURTHER); + } + RESPONSE("I'm going to go my own way for a while."); + SUCCESS(TALK_LEAVE); + if (!p->has_disease(DI_ASKED_TO_LEAD)) { + RESPONSE("I'd like to lead for a while."); + TRIAL(TALK_TRIAL_PERSUADE, persuade); + SUCCESS(TALK_PLAYER_LEADS); + SUCCESS_ACTION(&talk_function::follow); + FAILURE(TALK_LEADER_STAYS); + FAILURE_OPINION(0, 0, -1, -1, 0); + RESPONSE("Step aside. I'm leader now."); + TRIAL(TALK_TRIAL_INTIMIDATE, 40); + SUCCESS(TALK_PLAYER_LEADS); + SUCCESS_ACTION(&talk_function::follow); + SUCCESS_OPINION(-1, 1, -1, 1, 0); + FAILURE(TALK_LEADER_STAYS); + FAILURE_OPINION(-1, 0, -1, 1, 0); + } + RESPONSE("Can I do anything for you?"); + SUCCESS(TALK_MISSION_LIST); + RESPONSE("Let's trade items."); + SUCCESS(TALK_NONE); + SUCCESS_ACTION(&talk_function::start_trade); + RESPONSE("Let's go."); + SUCCESS(TALK_DONE); + } break; + + case TALK_LEAVE: + RESPONSE("Nah, I'm just kidding."); + SUCCESS(TALK_NONE); + RESPONSE("Yeah, I'm sure. Bye."); + SUCCESS_ACTION(&talk_function::leave); + SUCCESS(TALK_DONE); + break; + + case TALK_PLAYER_LEADS: + RESPONSE("Good. Something else..."); + SUCCESS(TALK_FRIEND); + RESPONSE("Alright, let's go."); + SUCCESS(TALK_DONE); + break; + + case TALK_LEADER_STAYS: + RESPONSE("Okay, okay."); + SUCCESS(TALK_NONE); + break; + + case TALK_HOW_MUCH_FURTHER: + RESPONSE("Okay, thanks."); + SUCCESS(TALK_NONE); + RESPONSE("Let's keep moving."); + SUCCESS(TALK_DONE); + break; + case TALK_FRIEND: RESPONSE("Combat commands..."); SUCCESS(TALK_COMBAT_COMMANDS); RESPONSE("Can I do anything for you?"); SUCCESS(TALK_MISSION_LIST); + SELECT_TEMP("Can you teach me anything?", 0); + SUCCESS(TALK_TRAIN); RESPONSE("Let's trade items."); SUCCESS(TALK_NONE); SUCCESS_ACTION(&talk_function::start_trade); @@ -681,6 +981,124 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) SUCCESS(TALK_NONE); } break; + case TALK_STRANGER_NEUTRAL: + case TALK_STRANGER_WARY: + case TALK_STRANGER_SCARED: + case TALK_STRANGER_FRIENDLY: + if (topic == TALK_STRANGER_NEUTRAL || topic == TALK_STRANGER_FRIENDLY) { + RESPONSE("Another survivor! We should travel together."); + SUCCESS(TALK_SUGGEST_FOLLOW); + RESPONSE("What are you doing?"); + SUCCESS(TALK_DESCRIBE_MISSION); + RESPONSE("Care to trade?"); + SUCCESS(TALK_NONE); + SUCCESS_ACTION(&talk_function::start_trade); + } else { + if (!g->u.unarmed_attack()) { + if (g->u.volume_carried() + g->u.weapon.volume() <= g->u.volume_capacity()){ + RESPONSE("&Put away weapon."); + SUCCESS(TALK_STRANGER_NEUTRAL); + SUCCESS_ACTION(&talk_function::player_weapon_away); + SUCCESS_OPINION(2, -2, 0, 0, 0); + } + RESPONSE("&Drop weapon."); + SUCCESS(TALK_STRANGER_NEUTRAL); + SUCCESS_ACTION(&talk_function::player_weapon_drop); + SUCCESS_OPINION(4, -3, 0, 0, 0); + } + int diff = 50 + p->personality.bravery - 2 * p->op_of_u.fear + + 2 * p->op_of_u.trust; + RESPONSE("Don't worry, I'm not going to hurt you."); + TRIAL(TALK_TRIAL_PERSUADE, diff); + SUCCESS(TALK_STRANGER_NEUTRAL); + SUCCESS_OPINION(1, -1, 0, 0, 0); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::flee); + } + if (!p->unarmed_attack()) { + RESPONSE("!Drop your weapon!"); + TRIAL(TALK_TRIAL_INTIMIDATE, 30); + SUCCESS(TALK_WEAPON_DROPPED); + SUCCESS_ACTION(&talk_function::drop_weapon); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::hostile); + } + RESPONSE("!Get out of here or I'll kill you."); + TRIAL(TALK_TRIAL_INTIMIDATE, 20); + SUCCESS(TALK_DONE); + SUCCESS_ACTION(&talk_function::flee); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::hostile); + break; + + case TALK_STRANGER_AGGRESSIVE: + case TALK_MUG: + if (!g->u.unarmed_attack()) { + int chance = 30 + p->personality.bravery - 3 * p->personality.aggression + + 2 * p->personality.altruism - 2 * p->op_of_u.fear + + 3 * p->op_of_u.trust; + RESPONSE("!Calm down. I'm not going to hurt you."); + TRIAL(TALK_TRIAL_PERSUADE, chance); + SUCCESS(TALK_STRANGER_WARY); + SUCCESS_OPINION(1, -1, 0, 0, 0); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::hostile); + RESPONSE("!Screw you, no."); + TRIAL(TALK_TRIAL_INTIMIDATE, chance - 5); + SUCCESS(TALK_STRANGER_SCARED); + SUCCESS_OPINION(-2, 1, 0, 1, 0); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::hostile); + RESPONSE("&Drop weapon."); + if (topic == TALK_MUG) { + SUCCESS(TALK_MUG); + } else { + SUCCESS(TALK_DEMAND_LEAVE); + } + SUCCESS_ACTION(&talk_function::player_weapon_drop); + } else if (topic == TALK_MUG) { + int chance = 35 + p->personality.bravery - 3 * p->personality.aggression + + 2 * p->personality.altruism - 2 * p->op_of_u.fear + + 3 * p->op_of_u.trust; + RESPONSE("!Calm down. I'm not going to hurt you."); + TRIAL(TALK_TRIAL_PERSUADE, chance); + SUCCESS(TALK_STRANGER_WARY); + SUCCESS_OPINION(1, -1, 0, 0, 0); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::hostile); + RESPONSE("!Screw you, no."); + TRIAL(TALK_TRIAL_INTIMIDATE, chance - 5); + SUCCESS(TALK_STRANGER_SCARED); + SUCCESS_OPINION(-2, 1, 0, 1, 0); + FAILURE(TALK_DONE); + FAILURE_ACTION(&talk_function::hostile); + RESPONSE("&Put hands up."); + SUCCESS(TALK_DONE); + SUCCESS_ACTION(&talk_function::start_mugging); + } + break; + + case TALK_DESCRIBE_MISSION: + RESPONSE("I see."); + SUCCESS(TALK_NONE); + RESPONSE("Bye."); + SUCCESS(TALK_DONE); + break; + + case TALK_WEAPON_DROPPED: + RESPONSE("Now get out of here."); + SUCCESS(TALK_DONE); + SUCCESS_OPINION(-1, -2, 0, -2, 0); + SUCCESS_ACTION(&talk_function::flee); + break; + + case TALK_DEMAND_LEAVE: + RESPONSE("Okay, I'm going."); + SUCCESS(TALK_DONE); + SUCCESS_OPINION(0, -1, 0, 0, 0); + SUCCESS_ACTION(&talk_function::player_leaving); + break; + case TALK_SIZE_UP: case TALK_LOOK_AT: case TALK_OPINION: @@ -773,6 +1191,11 @@ int topic_category(talk_topic topic) case TALK_COMBAT_COMMANDS: return 5; + case TALK_TRAIN: + case TALK_TRAIN_START: + case TALK_TRAIN_FORCE: + return 6; + case TALK_SIZE_UP: case TALK_LOOK_AT: case TALK_OPINION: @@ -809,7 +1232,9 @@ void talk_function::mission_success(game *g, npc *p) return; } int index = p->chatbin.missions_assigned[selected]; - p->op_of_u.owed += g->find_mission(index)->value; + mission *miss = g->find_mission(index); + npc_opinion tmp( 0, 0, 1 + (miss->value / 1000), -1, miss->value); + p->op_of_u += tmp; g->wrap_up_mission(index); } @@ -821,6 +1246,8 @@ void talk_function::mission_failure(game *g, npc *p) selected, p->chatbin.missions_assigned.size()); return; } + npc_opinion tmp( -1, 0, -1, 1, 0); + p->op_of_u += tmp; g->mission_failed(p->chatbin.missions_assigned[selected]); } @@ -896,6 +1323,68 @@ void talk_function::deny_follow(game *g, npc *p) { p->add_disease(DI_ASKED_TO_FOLLOW, 3600, g); } + +void talk_function::deny_lead(game *g, npc *p) +{ + p->add_disease(DI_ASKED_TO_LEAD, 3600, g); +} + +void talk_function::deny_equipment(game *g, npc *p) +{ + p->add_disease(DI_ASKED_FOR_ITEM, 600, g); +} + +void talk_function::hostile(game *g, npc *p) +{ + p->attitude = NPCATT_KILL; +} + +void talk_function::flee(game *g, npc *p) +{ + p->attitude = NPCATT_FLEE; +} + +void talk_function::leave(game *g, npc *p) +{ + p->attitude = NPCATT_NULL; +} + +void talk_function::start_mugging(game *g, npc *p) +{ + p->attitude = NPCATT_MUG; + g->add_msg("Pause to stay still. Any movement may cause %s to attack.", + p->name.c_str()); +} + +void talk_function::player_leaving(game *g, npc *p) +{ + p->attitude = NPCATT_WAIT_FOR_LEAVE; + p->patience = 15 - p->personality.aggression; +} + +void talk_function::drop_weapon(game *g, npc *p) +{ + g->m.add_item(p->posx, p->posy, p->remove_weapon()); +} + +void talk_function::player_weapon_away(game *g, npc *p) +{ + g->u.i_add(g->u.remove_weapon()); +} + +void talk_function::player_weapon_drop(game *g, npc *p) +{ + g->m.add_item(g->u.posx, g->u.posy, g->u.remove_weapon()); +} + +void talk_function::lead_to_safety(game *g, npc *p) +{ + g->give_mission(MISSION_REACH_SAFETY); + point target = g->find_mission( g->u.active_mission )->target; + p->goalx = target.x; + p->goaly = target.y; + p->attitude = NPCATT_LEAD; +} void talk_function::toggle_use_guns(game *g, npc *p) { @@ -932,6 +1421,29 @@ void talk_function::set_engagement_all(game *g, npc *p) p->combat_rules.engagement = ENGAGE_ALL; } +void talk_function::start_training(game *g, npc *p) +{ + int cost = 0, time = 0; + skill sk_used = sk_null; + itype_id style = itm_null; + if (p->chatbin.tempvalue < 0) { + cost = -800; + style = itype_id(0 - p->chatbin.tempvalue); + time = 30000; + } else { + sk_used = skill(p->chatbin.tempvalue); + cost = -200 * (1 + g->u.sklevel[sk_used]); + time = 10000 + 5000 * g->u.sklevel[sk_used]; + } + +// Pay for it + if (p->op_of_u.owed >= cost) + p->op_of_u.owed -= cost; + else if (!trade(g, p, cost, "Pay for training:")) + return; +// Then receive it + g->u.assign_activity(ACT_TRAIN, time, p->chatbin.tempvalue); +} void parse_tags(std::string &phrase, player *u, npc *me) { @@ -1023,7 +1535,7 @@ talk_topic dialogue::opt(talk_topic topic, game *g) std::vector colors; for (int i = 0; i < responses.size(); i++) { std::stringstream text; - text << i + 1 << ": "; + text << char('a' + i) << ": "; if (responses[i].trial != TALK_TRIAL_NONE) text << "[" << talk_trial_text[responses[i].trial] << " " << trial_chance(responses[i], alpha, beta) << "%] "; @@ -1081,7 +1593,7 @@ talk_topic dialogue::opt(talk_topic topic, game *g) do { ch = getch(); if (special_talk(ch) == TALK_NONE) - ch -= '1'; + ch -= 'a'; } while (special_talk(ch) == TALK_NONE && (ch < 0 || ch >= options.size())); okay = false; if (special_talk(ch) != TALK_NONE) @@ -1110,6 +1622,9 @@ talk_topic dialogue::opt(talk_topic topic, game *g) talk_response chosen = responses[ch]; if (chosen.mission_index != -1) beta->chatbin.mission_selected = chosen.mission_index; + if (chosen.tempvalue != -1) + beta->chatbin.tempvalue = chosen.tempvalue; + talk_function effect; if (chosen.trial == TALK_TRIAL_NONE || rng(0, 99) < trial_chance(chosen, alpha, beta)) { @@ -1191,10 +1706,10 @@ Tab key to switch lists, letters to pick items, Enter to finalize, Esc to quit\n getting_yours[i] = false; } - int cash = cost;// How much cash you get in the deal (negative = losing money) + int cash = cost;// How much cash you get in the deal (negative = losing money) bool focus_them = true; // Is the focus on them? bool update = true; // Re-draw the screen? - int them_off = 0, you_off = 0; // Offset from the start of the list + int them_off = 0, you_off = 0;// Offset from the start of the list char ch, help; do { diff --git a/output.cpp b/output.cpp index 8bf474e6d9..4339524fb4 100644 --- a/output.cpp +++ b/output.cpp @@ -1,5 +1,3 @@ -#ifndef _OUTPUT_H_ -#define _OUTPUT_H_ #if (defined _WIN32 || defined WINDOWS) #include "catacurse.h" @@ -502,22 +500,39 @@ int menu_vec(const char *mes, std::vector options) if (options[i].length() + 6 > width) width = options[i].length() + 6; } - WINDOW* w = newwin(height, width, 6, 10); + WINDOW* w = newwin(height, width, 1, 10); wattron(w, c_white); wborder(w, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); mvwprintw(w, 1, 1, title.c_str()); for (int i = 0; i < options.size(); i++) - mvwprintw(w, i + 2, 1, "%d: %s", i + 1, options[i].c_str()); + mvwprintw(w, i + 2, 1, "%c: %s", (i < 9? i + '1' : + (i == 9? '0' : 'a' + i - 10)), + options[i].c_str()); long ch; wrefresh(w); + int res; do + { ch = getch(); - while (ch < '1' || ch >= '1' + options.size()); + if (ch >= '1' && ch <= '9') + res = ch - '1' + 1; + else + if (ch == '0') + res = 10; + else + if (ch >= 'a' && ch <= 'z') + res = ch - 'a' + 11; + else + res = -1; + if (res > options.size()) + res = -1; + } + while (res == -1); werase(w); wrefresh(w); delwin(w); - return (ch - '1' + 1); + return (res); } int menu(const char *mes, ...) @@ -731,4 +746,22 @@ char rand_char() } return '?'; } -#endif + +// this translates symbol y, u, n, b to NW, NE, SE, SW lines correspondingly +// h, j, c to horizontal, vertical, cross correspondingly +long special_symbol (char sym) +{ + switch (sym) + { + case 'j': return LINE_XOXO; + case 'h': return LINE_OXOX; + case 'c': return LINE_XXXX; + case 'y': return LINE_OXXO; + case 'u': return LINE_OOXX; + case 'n': return LINE_XOOX; + case 'b': return LINE_XXOO; + default: return sym; + } +} + + diff --git a/output.h b/output.h index 9c3f517197..a9c8c425e0 100644 --- a/output.h +++ b/output.h @@ -50,5 +50,6 @@ nc_color invert_color(nc_color c); nc_color red_background(nc_color c); nc_color rand_color(); char rand_char(); +long special_symbol (char sym); #endif diff --git a/overmap.cpp b/overmap.cpp index e73966f642..ee7a17ba20 100644 --- a/overmap.cpp +++ b/overmap.cpp @@ -160,6 +160,20 @@ std::vector overmap::monsters_at(int x, int y) return ret; } +bool overmap::is_safe(int x, int y) +{ + std::vector mons = monsters_at(x, y); + if (monsters_at(x, y).empty()) + return true; + + bool safe = true; + for (int n = 0; n < mons.size() && safe; n++) + safe = mons[n]->is_safe(); + + return safe; +} + + bool& overmap::seen(int x, int y) { if (x < 0 || x >= OMAPX || y < 0 || y >= OMAPY) { @@ -815,6 +829,8 @@ void overmap::draw(WINDOW *w, game *g, int &cursx, int &cursy, vert = overmap(g, posx, posy + 1, posz); // Now actually draw the map + bool csee = false; + oter_id ccur_ter; for (int i = -25; i < 25; i++) { for (int j = -12; j <= (ch == 'j' ? 13 : 12); j++) { omx = cursx + i; @@ -903,9 +919,11 @@ void overmap::draw(WINDOW *w, game *g, int &cursx, int &cursy, ter_color = c_dkgray; ter_sym = '#'; } - if (j == 0 && i == 0) + if (j == 0 && i == 0) { mvwputch_hi (w, 12, 25, ter_color, ter_sym); - else + csee = see; + ccur_ter = cur_ter; + } else mvwputch (w, 12 + j, 25 + i, ter_color, ter_sym); } } @@ -948,10 +966,10 @@ void overmap::draw(WINDOW *w, game *g, int &cursx, int &cursy, mvwputch(w, j, i, c_black, 'x'); } - if (seen(cursx, cursy)) { - mvwputch(w, 1, 51, oterlist[cur_ter].color, oterlist[cur_ter].sym); - mvwprintz(w, 1, 53, oterlist[cur_ter].color, "%s", - oterlist[cur_ter].name.c_str()); + if (csee) { + mvwputch(w, 1, 51, oterlist[ccur_ter].color, oterlist[ccur_ter].sym); + mvwprintz(w, 1, 53, oterlist[ccur_ter].color, "%s", + oterlist[ccur_ter].name.c_str()); } else mvwprintz(w, 1, 51, c_dkgray, "# Unexplored"); @@ -1461,7 +1479,7 @@ void overmap::build_lab(int x, int y, int s) ter(finalex, finaley) != ot_lab_core); ter(finalex, finaley) = ot_lab_finale; } - zg.push_back(mongroup(mcat_lab, (x * 2), (y * 2), s, 60)); + zg.push_back(mongroup(mcat_lab, (x * 2), (y * 2), s, 400)); } void overmap::build_anthill(int x, int y, int s) @@ -2102,7 +2120,7 @@ void overmap::place_special(overmap_special special, point p) } if (special.flags & mfb(OMS_FLAG_3X3_SECOND)) { - int startx = -1, starty = -1; + int startx = p.x - 1, starty = p.y; if (is_road(p.x, p.y - 1)) { // Road to north startx = p.x - 1; starty = p.y; @@ -2211,12 +2229,15 @@ void overmap::place_mongroups() int swamp_count = 0; for (int sx = x - 3; sx <= x + 3; sx++) { for (int sy = y - 3; sy <= y + 3; sy++) { - if (ter(sx, sy) == ot_forest_water || is_river(ter(sx, sy))) + if (ter(sx, sy) == ot_forest_water) + swamp_count += 2; + else if (is_river(ter(sx, sy))) swamp_count++; } } - if (swamp_count >= 15) // 30% swamp! - zg.push_back(mongroup(mcat_swamp, x * 2, y * 2, 3, rng(200, 800))); + if (swamp_count >= 25) // ~25% swamp or ~50% river + zg.push_back(mongroup(mcat_swamp, x * 2, y * 2, 3, + rng(swamp_count * 8, swamp_count * 25))); } } @@ -2298,10 +2319,10 @@ void overmap::save(std::string name, int x, int y, int z) fout << "T " << radios[i].x << " " << radios[i].y << " " << radios[i].strength << " " << std::endl << radios[i].message << std::endl; -/* BUGGY - omit for now + for (int i = 0; i < npcs.size(); i++) fout << "n " << npcs[i].save_info() << std::endl; -*/ + fout.close(); } @@ -2362,7 +2383,7 @@ void overmap::open(game *g, int x, int y, int z) std::string npcdata; getline(fin, npcdata); npc tmp; - tmp.load_info(npcdata); + tmp.load_info(g, npcdata); npcs.push_back(tmp); } else if (datatype == 'I' || datatype == 'C' || datatype == 'W' || datatype == 'w' || datatype == 'c') { diff --git a/overmap.h b/overmap.h index 55234cfbe8..5ba59a514d 100644 --- a/overmap.h +++ b/overmap.h @@ -81,6 +81,7 @@ class overmap oter_id& ter(int x, int y); std::vector monsters_at(int x, int y); + bool is_safe(int x, int y); // true if monsters_at is empty, or only woodland bool& seen(int x, int y); bool has_note(int x, int y); diff --git a/player.cpp b/player.cpp index 4e4abd470d..c4fc4c2f0b 100644 --- a/player.cpp +++ b/player.cpp @@ -18,6 +18,7 @@ #endif nc_color encumb_color(int level); +bool activity_is_suspendable(activity_type type); player::player() { @@ -30,7 +31,8 @@ player::player() per_cur = 8; per_max = 8; underwater = false; - can_dodge = true; + dodges_left = 1; + blocks_left = 1; power_level = 0; max_power_level = 0; hunger = 0; @@ -42,13 +44,17 @@ player::player() radiation = 0; cash = 0; recoil = 0; + driving_recoil = 0; scent = 500; + health = 0; name = ""; male = true; inv_sorted = true; moves = 100; oxygen = 0; active_mission = -1; + in_vehicle = false; + style_selected = itm_null; xp_pool = 0; last_item = itype_id(itm_null); for (int i = 0; i < num_skill_types; i++) { @@ -65,6 +71,11 @@ player::player() mutation_category_level[i] = 0; } +player::player(const player &rhs) +{ + *this = rhs; +} + player::~player() { } @@ -80,7 +91,8 @@ player& player::operator= (player rhs) per_cur = rhs.per_cur; per_max = rhs.per_max; underwater = rhs.underwater; - can_dodge = rhs.can_dodge; + dodges_left = rhs.dodges_left; + blocks_left = rhs.blocks_left; power_level = rhs.power_level; max_power_level = rhs.max_power_level; hunger = rhs.hunger; @@ -92,6 +104,8 @@ player& player::operator= (player rhs) radiation = rhs.radiation; cash = rhs.cash; recoil = rhs.recoil; + driving_recoil = rhs.driving_recoil; + in_vehicle = rhs.in_vehicle; scent = rhs.scent; name = rhs.name; male = rhs.male; @@ -115,6 +129,13 @@ player& player::operator= (player rhs) for (int i = 0; i < rhs.inv.size(); i++) inv.add_stack(rhs.inv.stack_at(i)); + worn.clear(); + for (int i = 0; i < rhs.worn.size(); i++) + worn.push_back(rhs.worn[i]); + + style_selected = rhs.style_selected; + weapon = rhs.weapon; + return (*this); } @@ -122,6 +143,7 @@ void player::normalize(game *g) { ret_null = item(g->itypes[0], 0); weapon = item(g->itypes[0], 0); + style_selected = itm_null; for (int i = 0; i < num_hp_parts; i++) { hp_max[i] = 60 + str_max * 3; if (has_trait(PF_TOUGH)) @@ -140,7 +162,8 @@ void player::reset(game *g) int_cur = int_max; per_cur = per_max; // We can dodge again! - can_dodge = true; + dodges_left = 1; + blocks_left = 1; // Didn't just pick something up last_item = itype_id(itm_null); // Bionic buffs @@ -162,7 +185,7 @@ void player::reset(game *g) // Trait / mutation buffs if (has_trait(PF_THICK_SCALES)) - dex_cur--; + dex_cur -= 2; if (has_trait(PF_CHITIN2) || has_trait(PF_CHITIN3)) dex_cur--; if (has_trait(PF_COMPOUND_EYES) && !wearing_something_on(bp_eyes)) @@ -285,8 +308,8 @@ int player::current_speed(game *g) newmoves += morale_bonus; } - if (radiation > 10) { - int rad_penalty = radiation / 10; + if (radiation >= 40) { + int rad_penalty = radiation / 40; if (rad_penalty > 20) rad_penalty = 20; newmoves -= rad_penalty; @@ -308,15 +331,12 @@ int player::current_speed(game *g) if (g != NULL) { if (has_trait(PF_SUNLIGHT_DEPENDENT) && !g->is_in_sunlight(posx, posy)) newmoves -= (g->light_level() >= 12 ? 5 : 10); - if ((has_trait(PF_COLDBLOOD) || has_trait(PF_COLDBLOOD2) || - has_trait(PF_COLDBLOOD3)) && g->temperature < 65) { - if (has_trait(PF_COLDBLOOD3)) - newmoves -= int( (65 - g->temperature) / 2); - else if (has_trait(PF_COLDBLOOD2)) - newmoves -= int( (65 - g->temperature) / 3); - else - newmoves -= int( (65 - g->temperature) / 2); - } + if (has_trait(PF_COLDBLOOD3) && g->temperature < 60) + newmoves -= int( (65 - g->temperature) / 2); + else if (has_trait(PF_COLDBLOOD2) && g->temperature < 60) + newmoves -= int( (65 - g->temperature) / 3); + else if (has_trait(PF_COLDBLOOD) && g->temperature < 60) + newmoves -= int( (65 - g->temperature) / 5); } if (has_artifact_with(AEP_SPEED_UP)) @@ -407,6 +427,8 @@ nc_color player::color() { if (has_disease(DI_ONFIRE)) return c_red; + if (has_disease(DI_STUNNED)) + return c_ltblue; if (has_disease(DI_BOOMERED)) return c_pink; if (underwater) @@ -420,12 +442,20 @@ void player::load_info(game *g, std::string data) { std::stringstream dump; dump << data; + int inveh; + int styletmp; dump >> posx >> posy >> str_cur >> str_max >> dex_cur >> dex_max >> int_cur >> int_max >> per_cur >> per_max >> power_level >> max_power_level >> hunger >> thirst >> fatigue >> stim >> - pain >> pkill >> radiation >> cash >> recoil >> scent >> moves >> - underwater >> can_dodge >> oxygen >> active_mission >> xp_pool >> - male >> health; + pain >> pkill >> radiation >> cash >> recoil >> driving_recoil >> + inveh >> scent >> moves >> underwater >> dodges_left >> blocks_left >> + oxygen >> active_mission >> xp_pool >> male >> health >> styletmp; + + activity.load_info(dump); + backlog.load_info(dump); + + in_vehicle = inveh != 0; + style_selected = itype_id(styletmp); for (int i = 0; i < PF_MAX2; i++) dump >> my_traits[i]; @@ -441,8 +471,14 @@ void player::load_info(game *g, std::string data) for (int i = 0; i < num_skill_types; i++) dump >> sklevel[i] >> skexercise[i]; + int numstyles, typetmp; + dump >> numstyles; + for (int i = 0; i < numstyles; i++) { + dump >> typetmp; + styles.push_back( itype_id(typetmp) ); + } + int numill; - int typetmp; disease illtmp; dump >> numill; for (int i = 0; i < numill; i++) { @@ -493,10 +529,12 @@ std::string player::save_info() per_cur << " " << per_max << " " << power_level << " " << max_power_level << " " << hunger << " " << thirst << " " << fatigue << " " << stim << " " << pain << " " << pkill << " " << radiation << - " " << cash << " " << recoil << " " << scent << " " << moves << " " << - underwater << " " << can_dodge << " " << oxygen << " " << - active_mission << " " << xp_pool << " " << male << " " << health << - " "; + " " << cash << " " << recoil << " " << driving_recoil << " " << + (in_vehicle? 1 : 0) << " " << scent << " " << moves << " " << + underwater << " " << dodges_left << " " << blocks_left << " " << + oxygen << " " << active_mission << " " << xp_pool << " " << male << + " " << health << " " << style_selected << " " << + activity.save_info() << " " << backlog.save_info() << " "; for (int i = 0; i < PF_MAX2; i++) dump << my_traits[i] << " "; @@ -509,6 +547,10 @@ std::string player::save_info() for (int i = 0; i < num_skill_types; i++) dump << int(sklevel[i]) << " " << skexercise[i] << " "; + dump << styles.size() << " "; + for (int i = 0; i < styles.size(); i++) + dump << int(styles[i]); + dump << illness.size() << " "; for (int i = 0; i < illness.size(); i++) dump << int(illness[i].type) << " " << illness[i].duration << " "; @@ -812,7 +854,8 @@ Strength - 4; Dexterity - 4; Intelligence - 4; Dexterity - 4"); if (line < 9) { mvwprintz(w_skills, line, 1, c_ltblue, "%s:", skill_name(skill(i)).c_str()); - mvwprintz(w_skills, line,19, c_ltblue, "%d (%s%d%%%%)", sklevel[i], + mvwprintz(w_skills, line,19, c_ltblue, "%d%s(%s%d%%%%)", sklevel[i], + (sklevel[i] < 10 ? " " : ""), (skexercise[i] < 10 && skexercise[i] >= 0 ? " " : ""), (skexercise[i] < 0 ? 0 : skexercise[i])); line++; @@ -1347,13 +1390,14 @@ void player::disp_status(WINDOW *w, game *g) { mvwprintz(w, 1, 0, c_ltgray, "Weapon: %s", weapname().c_str()); if (weapon.is_gun()) { - if (recoil >= 36) + int adj_recoil = recoil + driving_recoil; + if (adj_recoil >= 36) mvwprintz(w, 1, 30, c_red, "Recoil"); - else if (recoil >= 20) + else if (adj_recoil >= 20) mvwprintz(w, 1, 30, c_ltred, "Recoil"); - else if (recoil >= 4) + else if (adj_recoil >= 4) mvwprintz(w, 1, 30, c_yellow, "Recoil"); - else if (recoil > 0) + else if (adj_recoil > 0) mvwprintz(w, 1, 30, c_ltgray, "Recoil"); } @@ -1409,36 +1453,100 @@ void player::disp_status(WINDOW *w, game *g) else if (pain - pkill > 0) mvwprintz(w, 3, 0, c_yellow, "Minor pain"); - nc_color col_str = c_white, col_dex = c_white, col_int = c_white, - col_per = c_white, col_spd = c_white; - if (str_cur < str_max) - col_str = c_red; - if (str_cur > str_max) - col_str = c_green; - if (dex_cur < dex_max) - col_dex = c_red; - if (dex_cur > dex_max) - col_dex = c_green; - if (int_cur < int_max) - col_int = c_red; - if (int_cur > int_max) - col_int = c_green; - if (per_cur < per_max) - col_per = c_red; - if (per_cur > per_max) - col_per = c_green; - int spd_cur = current_speed(g); - if (spd_cur < 100) - col_spd = c_red; - if (spd_cur > 100) - col_spd = c_green; - - mvwprintz(w, 3, 20, col_str, "Str %s%d", str_cur >= 10 ? "" : " ", str_cur); - mvwprintz(w, 3, 27, col_dex, "Dex %s%d", dex_cur >= 10 ? "" : " ", dex_cur); - mvwprintz(w, 3, 34, col_int, "Int %s%d", int_cur >= 10 ? "" : " ", int_cur); - mvwprintz(w, 3, 41, col_per, "Per %s%d", per_cur >= 10 ? "" : " ", per_cur); - mvwprintz(w, 3, 48, col_spd, "Spd %s%d", spd_cur >= 10 ? "" : " ", spd_cur); + vehicle &veh = g->m.veh_at (posx, posy); + int dmor = in_vehicle && veh.type != veh_null? 0 : 9; + int morale_cur = morale_level (); + nc_color col_morale = c_white; + if (morale_cur > 0) + col_morale = c_green; + else + if (morale_cur < 0) + col_morale = c_red; + if (morale_cur >= 1000) + mvwprintz(w, 3, 11 + dmor, col_morale, "M hi"); + else if (morale_cur <= -100) + mvwprintz(w, 3, 11 + dmor, col_morale, "M low"); + else + mvwprintz(w, 3, 11 + dmor, col_morale, "M %3d", morale_cur); + + if (in_vehicle && veh.type != veh_null) + { + veh.print_fuel_indicator (w, 3, 49); + nc_color col_indf1 = c_ltgray; + + float strain = veh.strain(); + nc_color col_vel = strain <= 0? c_ltblue : + (strain <= 0.2? c_yellow : + (strain <= 0.4? c_ltred : c_red)); + + bool has_turrets = false; + for (int p = 0; p < veh.parts.size(); p++) + if (veh.part_flag (p, vpf_turret)) + { + has_turrets = true; + break; + } + + if (has_turrets) + { + mvwprintz(w, 3, 25, col_indf1, "Gun:"); + mvwprintz(w, 3, 29, veh.turret_mode? c_ltred : c_ltblue, veh.turret_mode? "auto" : "off "); + } + + if (veh.cruise_on) + { + mvwprintz(w, 3, 34, col_indf1, "{mph....>....}"); + mvwprintz(w, 3, 38, col_vel, "%4d", veh.velocity / 100); + mvwprintz(w, 3, 43, c_ltgreen, "%4d", veh.cruise_velocity / 100); + } + else + { + mvwprintz(w, 3, 34, col_indf1, " {mph....} "); + mvwprintz(w, 3, 40, col_vel, "%4d", veh.velocity / 100); + } + + if (veh.velocity != 0) + { + nc_color col_indc = veh.skidding? c_red : c_green; + int dfm = veh.face.dir() - veh.move.dir(); + mvwprintz(w, 3, 21, col_indc, dfm < 0? "L" : "."); + mvwprintz(w, 3, 22, col_indc, dfm == 0? "0" : "."); + mvwprintz(w, 3, 23, col_indc, dfm > 0? "R" : "."); + } + } + else + { + nc_color col_str = c_white, col_dex = c_white, col_int = c_white, + col_per = c_white, col_spd = c_white; + if (str_cur < str_max) + col_str = c_red; + if (str_cur > str_max) + col_str = c_green; + if (dex_cur < dex_max) + col_dex = c_red; + if (dex_cur > dex_max) + col_dex = c_green; + if (int_cur < int_max) + col_int = c_red; + if (int_cur > int_max) + col_int = c_green; + if (per_cur < per_max) + col_per = c_red; + if (per_cur > per_max) + col_per = c_green; + int spd_cur = current_speed(); + if (current_speed() < 100) + col_spd = c_red; + if (current_speed() > 100) + col_spd = c_green; + + mvwprintz(w, 3, 26, col_str, "St %s%d", str_cur >= 10 ? "" : " ", str_cur); + mvwprintz(w, 3, 32, col_dex, "Dx %s%d", dex_cur >= 10 ? "" : " ", dex_cur); + mvwprintz(w, 3, 38, col_int, "In %s%d", int_cur >= 10 ? "" : " ", int_cur); + mvwprintz(w, 3, 44, col_per, "Pe %s%d", per_cur >= 10 ? "" : " ", per_cur); + mvwprintz(w, 3, 50, col_spd, "S %s%d", spd_cur >= 10 ? "" : " ", spd_cur); + } } bool player::has_trait(int flag) @@ -1655,6 +1763,7 @@ int player::clairvoyance() { if (has_artifact_with(AEP_CLAIRVOYANCE)) return 3; + return 0; } bool player::has_two_arms() @@ -1679,7 +1788,7 @@ bool player::avoid_trap(trap* tr) return false; } -void player::pause() +void player::pause(game *g) { moves = 0; if (recoil > 0) { @@ -1690,6 +1799,17 @@ void player::pause() recoil = int(recoil / 2); } } + +// Meditation boost for Toad Style + if (weapon.type->id == itm_style_toad && activity.type == ACT_NULL) { + int arm_amount = 1 + (int_cur - 6) / 3 + (per_cur - 6) / 3; + int arm_max = (int_cur + per_cur) / 2; + if (arm_amount > 3) + arm_amount = 3; + if (arm_max > 20) + arm_max = 20; + add_disease(DI_ARMOR_BOOST, 2, g, arm_amount, arm_max); + } } int player::throw_range(int index) @@ -1708,9 +1828,9 @@ int player::throw_range(int index) ret -= int(tmp.volume() / 10); if (ret < 1) return 1; -// Cap at double our strength - if (ret > str_cur * 2) - return str_cur * 2; +// Cap at double our strength + skill + if (ret > str_cur * 2 + sklevel[sk_throw]) + return str_cur * 2 + sklevel[sk_throw]; return ret; } @@ -1842,7 +1962,13 @@ void player::hit(game *g, body_part bphurt, int side, int dam, int cut) if (dam <= 0) return; - g->cancel_activity_query("You were hurt!"); + rem_disease(DI_SPEED_BOOST); + if (dam >= 6) + rem_disease(DI_ARMOR_BOOST); + + + if (!is_npc()) + g->cancel_activity_query("You were hurt!"); if (has_artifact_with(AEP_SNAKES) && dam >= 6) { int snakes = int(dam / 6); @@ -1950,7 +2076,8 @@ void player::hurt(game *g, body_part bphurt, int side, int dam) if (dam <= 0) return; - g->cancel_activity_query("You were hurt!"); + if (!is_npc()) + g->cancel_activity_query("You were hurt!"); if (has_trait(PF_PAINRESIST)) painadd = dam / 3; @@ -2078,6 +2205,105 @@ void player::hurtall(int dam) } } +void player::hitall(game *g, int dam, int vary) +{ + if (has_disease(DI_SLEEP)) { + g->add_msg("You wake up!"); + rem_disease(DI_SLEEP); + } else if (has_disease(DI_LYING_DOWN)) + rem_disease(DI_LYING_DOWN); + + for (int i = 0; i < num_hp_parts; i++) { + int ddam = vary? dam * rng (100 - vary, 100) / 100 : dam; + int cut = 0; + absorb(g, (body_part) i, ddam, cut); + int painadd = 0; + hp_cur[i] -= ddam; + if (hp_cur[i] < 0) + hp_cur[i] = 0; + if (has_trait(PF_PAINRESIST)) + painadd = dam / 3 / 4; + else + painadd = dam / 2 / 4; + pain += painadd; + } +} + +void player::knock_back_from(game *g, int x, int y) +{ + if (x == posx && y == posy) + return; // No effect + point to(posx, posy); + if (x < posx) + to.x++; + if (x > posx) + to.x--; + if (y < posy) + to.y++; + if (y > posy) + to.y--; + + int t = 0; + bool u_see = (!is_npc() || g->u_see(to.x, to.y, t)); + + std::string You = (is_npc() ? name : "You"); + std::string s = (is_npc() ? "s" : ""); + +// First, see if we hit a monster + int mondex = g->mon_at(to.x, to.y); + if (mondex != -1) { + monster *z = &(g->z[mondex]); + hit(g, bp_torso, 0, z->type->size, 0); + add_disease(DI_STUNNED, 1, g); + if ((str_max - 6) / 4 > z->type->size) { + z->knock_back_from(g, posx, posy); // Chain reaction! + z->hurt((str_max - 6) / 4); + z->add_effect(ME_STUNNED, 1); + } else if ((str_max - 6) / 4 == z->type->size) { + z->hurt((str_max - 6) / 4); + z->add_effect(ME_STUNNED, 1); + } + + if (u_see) + g->add_msg("%s bounce%s off a %s!", + You.c_str(), s.c_str(), z->name().c_str()); + + return; + } + + int npcdex = g->npc_at(to.x, to.y); + if (npcdex != -1) { + npc *p = &(g->active_npc[npcdex]); + hit(g, bp_torso, 0, 3, 0); + add_disease(DI_STUNNED, 1, g); + p->hit(g, bp_torso, 0, 3, 0); + if (u_see) + g->add_msg("%s bounce%s off %s!", You.c_str(), s.c_str(), p->name.c_str()); + + return; + } + +// If we're still in the function at this point, we're actually moving a tile! + if (g->m.move_cost(to.x, to.y) == 0) { // Wait, it's a wall (or water) + + if (g->m.has_flag(liquid, to.x, to.y)) { + if (!is_npc()) + g->plswim(to.x, to.y); +// TODO: NPCs can't swim! + } else { // It's some kind of wall. + hurt(g, bp_torso, 0, 3); + add_disease(DI_STUNNED, 2, g); + if (u_see) + g->add_msg("%s bounce%s off a %s.", name.c_str(), s.c_str(), + g->m.tername(to.x, to.y).c_str()); + } + + } else { // It's no wall + posx = to.x; + posy = to.y; + } +} + int player::hp_percentage() { int total_cur = 0, total_max = 0; @@ -2122,13 +2348,17 @@ void player::infect(dis_type type, body_part vector, int strength, add_disease(type, duration, g); } -void player::add_disease(dis_type type, int duration, game *g) +void player::add_disease(dis_type type, int duration, game *g, + int intensity, int max_intensity) { bool found = false; int i = 0; while ((i < illness.size()) && !found) { if (illness[i].type == type) { illness[i].duration += duration; + illness[i].intensity += intensity; + if (max_intensity != -1 && illness[i].intensity > max_intensity) + illness[i].intensity = max_intensity; found = true; } i++; @@ -2136,7 +2366,7 @@ void player::add_disease(dis_type type, int duration, game *g) if (!found) { if (!is_npc()) dis_msg(g, type); - disease tmp(type, duration); + disease tmp(type, duration, intensity); illness.push_back(tmp); } // activity.type = ACT_NULL; @@ -2168,6 +2398,15 @@ int player::disease_level(dis_type type) return 0; } +int player::disease_intensity(dis_type type) +{ + for (int i = 0; i < illness.size(); i++) { + if (illness[i].type == type) + return illness[i].intensity; + } + return 0; +} + void player::add_addiction(add_type type, int strength) { if (type == ADD_NULL) @@ -2415,7 +2654,8 @@ void player::suffer(game *g) use_charges(itm_inhaler, 1); else { add_disease(DI_ASTHMA, 50 * rng(1, 4), g); - g->cancel_activity_query("You have an asthma attack!"); + if (!is_npc()) + g->cancel_activity_query("You have an asthma attack!"); } } @@ -2507,12 +2747,12 @@ void player::suffer(game *g) mutate(g); if (is_wearing(itm_hazmat_suit)) { - if (radiation < 100 * int(g->m.radiation(posx, posy) / 12)) - radiation += rng(0, g->m.radiation(posx, posy) / 12); - } else if (radiation < 100 * int(g->m.radiation(posx, posy) / 4)) - radiation += rng(0, g->m.radiation(posx, posy) / 4); + if (radiation < int((100 * g->m.radiation(posx, posy)) / 20)) + radiation += rng(0, g->m.radiation(posx, posy) / 20); + } else if (radiation < int((100 * g->m.radiation(posx, posy)) / 8)) + radiation += rng(0, g->m.radiation(posx, posy) / 8); - if (rng(1, 1000) < radiation && (int(g->turn) % 150 == 0 || radiation > 2000)){ + if (rng(1, 2500) < radiation && (int(g->turn) % 150 == 0 || radiation > 2000)){ mutate(g); if (radiation > 2000) radiation = 2000; @@ -2561,8 +2801,8 @@ void player::suffer(game *g) void player::vomit(game *g) { g->add_msg("You throw up heavily!"); - hunger += 30; - thirst += 30; + hunger += rng(30, 50); + thirst += rng(30, 50); moves -= 100; for (int i = 0; i < illness.size(); i++) { if (illness[i].type == DI_FOODPOISON) { @@ -2841,6 +3081,13 @@ item player::remove_weapon() { item tmp = weapon; weapon = ret_null; +// We need to remove any boosts related to our style + rem_disease(DI_ATTACK_BOOST); + rem_disease(DI_DODGE_BOOST); + rem_disease(DI_DAMAGE_BOOST); + rem_disease(DI_SPEED_BOOST); + rem_disease(DI_ARMOR_BOOST); + rem_disease(DI_VIPER_COMBO); return tmp; } @@ -3481,27 +3728,41 @@ bool player::eat(game *g, int index) bool player::wield(game *g, int index) { - if (weapon.type->id == itm_bio_claws) { - g->add_msg("You cannot unwield your claws! Withdraw them with 'p'."); + if (weapon.has_flag(IF_NO_UNWIELD)) { + g->add_msg("You cannot unwield your %s! Withdraw them with 'p'.", + weapon.tname().c_str()); return false; } if (index == -3) { - if (!is_armed()) { - g->add_msg("You are already wielding nothing."); - return false; + bool pickstyle = (!styles.empty()); + if (weapon.is_style()) + remove_weapon(); + else if (!is_armed()) { + if (!pickstyle) { + g->add_msg("You are already wielding nothing."); + return false; + } } else if (volume_carried() + weapon.volume() < volume_capacity()) { inv.push_back(remove_weapon()); inv_sorted = false; moves -= 20; recoil = 0; - return true; + if (!pickstyle) + return true; } else if (query_yn("No space in inventory for your %s. Drop it?", weapon.tname(g).c_str())) { g->m.add_item(posx, posy, remove_weapon()); recoil = 0; - return true; + if (!pickstyle) + return true; } else return false; + + if (pickstyle) { + weapon = item( g->itypes[style_selected], 0 ); + weapon.invlet = ':'; + return true; + } } if (index == -1) { g->add_msg("You're already wielding that!"); @@ -3557,6 +3818,19 @@ bool player::wield(game *g, int index) } +void player::pick_style(game *g) // Style selection menu +{ + std::vector options; + options.push_back("No style"); + for (int i = 0; i < styles.size(); i++) + options.push_back( g->itypes[styles[i]]->name ); + int selection = menu_vec("Select a style", options); + if (selection >= 2) + style_selected = styles[selection - 2]; + else + style_selected = itm_null; +} + bool player::wear(game *g, char let) { item* to_wear = NULL; @@ -3869,6 +4143,11 @@ press 'U' while wielding the unloaded gun.", gun->tname(g).c_str()); void player::read(game *g, char ch) { + vehicle &veh = g->m.veh_at (posx, posy); + if (veh.type != veh_null && veh.player_in_control (this)) { + g->add_msg("It's bad idea to read while driving."); + return; + } if (morale_level() < MIN_MORALE_READ) { // See morale.h g->add_msg("What's the point of reading? (Your morale is too low!)"); return; @@ -3948,6 +4227,12 @@ void player::read(game *g, char ch) void player::try_to_sleep(game *g) { + if (g->m.ter(posx, posy) == t_bed) + g->add_msg("This bed is a comfortable place to sleep."); + else if (g->m.ter(posx, posy) != t_floor) + g->add_msg("It's %shard to get to sleep on this %s.", + terlist[g->m.ter(posx, posy)].movecost <= 2 ? "a little " : "", + terlist[g->m.ter(posx, posy)].name.c_str()); add_disease(DI_LYING_DOWN, 300, g); } @@ -3958,7 +4243,12 @@ bool player::can_sleep(game *g) sleepy -= 3; if (has_trait(PF_INSOMNIA)) sleepy -= 8; - if (g->m.ter(posx, posy) == t_bed) + + int vpart = -1; + vehicle &veh = g->m.veh_at (posx, posy, vpart); + if (veh.type != veh_null && veh.part_with_feature (vpart, vpf_seat) >= 0) + sleepy += 4; + else if (g->m.ter(posx, posy) == t_bed) sleepy += 5; else if (g->m.ter(posx, posy) == t_floor) sleepy += 1; @@ -4050,6 +4340,7 @@ int player::armor_bash(body_part bp) ret += 2; if (has_trait(PF_SHELL) && bp == bp_torso) ret += 6; + ret += rng(0, disease_intensity(DI_ARMOR_BOOST)); return ret; } @@ -4088,6 +4379,7 @@ int player::armor_cut(body_part bp) ret += 8; if (has_trait(PF_SHELL) && bp == bp_torso) ret += 14; + ret += rng(0, disease_intensity(DI_ARMOR_BOOST)); return ret; } @@ -4244,6 +4536,8 @@ void player::practice(skill s, int amount) { skill savant = sk_null; int savant_level = 0, savant_exercise = 0; + if (skexercise[s] < 0) + amount += (amount >= -1 * skexercise[s] ? -1 * skexercise[s] : amount); if (has_trait(PF_SAVANT)) { // Find our best skill for (int i = 1; i < num_skill_types; i++) { @@ -4267,6 +4561,23 @@ void player::practice(skill s, int amount) } } +void player::assign_activity(activity_type type, int moves, int index) +{ + if (backlog.type == type && backlog.index == index && + query_yn("Resume task?")) { + activity = backlog; + backlog = player_activity(); + } else + activity = player_activity(type, moves, index); +} + +void player::cancel_activity() +{ + if (activity_is_suspendable(activity.type)) + backlog = activity; + activity.type = ACT_NULL; +} + std::vector player::has_ammo(ammotype at) { std::vector ret; @@ -4302,7 +4613,100 @@ std::string player::weapname(bool charges) return dump.str(); } else if (weapon.is_null()) return "fists"; - else + + else if (weapon.is_style()) { // Styles get bonus-bars! + std::stringstream dump; + dump << weapon.tname(); + + switch (weapon.type->id) { + case itm_style_capoeira: + if (has_disease(DI_DODGE_BOOST)) + dump << " +Dodge"; + if (has_disease(DI_ATTACK_BOOST)) + dump << " +Attack"; + break; + + case itm_style_ninjutsu: + case itm_style_leopard: + if (has_disease(DI_ATTACK_BOOST)) + dump << " +Attack"; + break; + + case itm_style_crane: + if (has_disease(DI_DODGE_BOOST)) + dump << " +Dodge"; + break; + + case itm_style_dragon: + if (has_disease(DI_DAMAGE_BOOST)) + dump << " +Damage"; + break; + + case itm_style_tiger: { + dump << " ["; + int intensity = disease_intensity(DI_DAMAGE_BOOST); + for (int i = 1; i <= 5; i++) { + if (intensity >= i * 2) + dump << "*"; + else + dump << "."; + } + dump << "]"; + } break; + + case itm_style_centipede: { + dump << " ["; + int intensity = disease_intensity(DI_SPEED_BOOST); + for (int i = 1; i <= 8; i++) { + if (intensity >= i * 4) + dump << "*"; + else + dump << "."; + } + dump << "]"; + } break; + + case itm_style_venom_snake: { + dump << " ["; + int intensity = disease_intensity(DI_VIPER_COMBO); + for (int i = 1; i <= 2; i++) { + if (intensity >= i) + dump << "C"; + else + dump << "."; + } + dump << "]"; + } break; + + case itm_style_lizard: { + dump << " ["; + int intensity = disease_intensity(DI_ATTACK_BOOST); + for (int i = 1; i <= 4; i++) { + if (intensity >= i) + dump << "*"; + else + dump << "."; + } + dump << "]"; + } break; + + case itm_style_toad: { + dump << " ["; + int intensity = disease_intensity(DI_ARMOR_BOOST); + for (int i = 1; i <= 5; i++) { + if (intensity >= 5 + i) + dump << "!"; + else if (intensity >= i) + dump << "*"; + else + dump << "."; + } + dump << "]"; + } break; + + } // switch (weapon.type->id) + return dump.str(); + } else return weapon.tname(); } @@ -4319,3 +4723,10 @@ nc_color encumb_color(int level) return c_red; } +bool activity_is_suspendable(activity_type type) +{ + if (type == ACT_NULL || type == ACT_RELOAD) + return false; + return true; +} + diff --git a/player.h b/player.h index c543614e28..1fd7cf5000 100644 --- a/player.h +++ b/player.h @@ -32,6 +32,7 @@ struct special_attack class player { public: player(); + player(const player &rhs); ~player(); player& operator= (player rhs); @@ -85,20 +86,46 @@ class player { bool unarmed_attack(); // False if we're wielding something; true for bionics bool avoid_trap(trap *tr); - void pause(); // '.' command; pauses & reduces recoil + void pause(game *g); // '.' command; pauses & reduces recoil + +// melee.cpp + int hit_mon(game *g, monster *z, bool allow_grab = true); + void hit_player(game *g, player &p, bool allow_grab = true); + + int base_damage(bool real_life = true, int stat = -999); + int base_to_hit(bool real_life = true, int stat = -999); + int hit_roll(); // Our basic hit roll, compared to our target's dodge roll - bool scored_crit(int target_dodge = 0); - int hit_mon(game *g, monster *z); // Handles hitting a monster up to its death -// hit_player returns false on a miss, and modifies bp, hitdam, and hitcut - bool hit_player(game *g, player &p, body_part &bp, int &hitdam, int &hitcut); - std::vector mutation_attacks(monster *z); - void stumble(game *g); + bool scored_crit(int target_dodge = 0); // Critical hit? + + int roll_bash_damage(monster *z, bool crit); + int roll_cut_damage(monster *z, bool crit); + int roll_stab_damage(monster *z, bool crit); + int roll_stuck_penalty(monster *z, bool stabbing); + + technique_id pick_technique(game *g, monster *z, player *p, + bool crit, bool allowgrab); + void perform_technique(technique_id technique, game *g, monster *z, player *p, + int &bash_dam, int &cut_dam, int &pierce_dam, int &pain); + + technique_id pick_defensive_technique(game *g, monster *z, player *p); + + void perform_defensive_technique(technique_id technique, game *g, monster *z, + player *p, body_part &bp_hit, int &side, + int &bash_dam, int &cut_dam, int &stab_dam); + + void perform_special_attacks(game *g, monster *z, player *p, + int &bash_dam, int &cut_dam, int &pierce_dam); + + std::vector mutation_attacks(monster *z, player *p); + void melee_special_effects(game *g, monster *z, player *p, bool crit, + int &bash_dam, int &cut_dam, int &stab_dam); + int dodge(game *g); // Returns the players's dodge, modded by clothing etc int dodge_roll(game *g);// For comparison to hit_roll() +// ranged.cpp int throw_range(int index); // Range of throwing item; -1:ERR 0:Can't throw - int base_damage (bool real_life = true); - int base_to_hit (bool real_life = true); int ranged_dex_mod (bool real_life = true); int ranged_per_mod (bool real_life = true); int throw_dex_mod (bool real_life = true); @@ -121,6 +148,10 @@ class player { void heal(hp_part healed, int dam); void healall(int dam); void hurtall(int dam); + // checks armor. if vary > 0, then damage to parts are random within 'vary' percent (1-100) + void hitall(game *g, int dam, int vary = 0); +// Sends us flying one tile + void knock_back_from(game *g, int x, int y); int hp_percentage(); // % of HP remaining, overall @@ -129,10 +160,12 @@ class player { void infect(dis_type type, body_part vector, int strength, int duration, game *g); // add_disease() does NOT give us a chance to save - void add_disease(dis_type type, int duration, game *g); + void add_disease(dis_type type, int duration, game *g, int intensity = 0, + int max_intensity = -1); void rem_disease(dis_type type); bool has_disease(dis_type type); int disease_level(dis_type type); + int disease_intensity(dis_type type); void add_addiction(add_type type, int strength); void rem_addiction(add_type type); @@ -145,6 +178,7 @@ class player { int lookup_item(char let); bool eat(game *g, int index); // Eat item; returns false on fail virtual bool wield(game *g, int index);// Wield item; returns false on fail + void pick_style(game *g); // Pick a style bool wear(game *g, char let); // Wear item; returns false on fail bool takeoff(game *g, char let);// Take off item; returns false on fail void use(game *g, char let); // Use a tool @@ -162,6 +196,9 @@ class player { void practice(skill s, int amount); // Practice a skill + void assign_activity(activity_type type, int moves, int index = -1); + void cancel_activity(); + int weight_carried(); int volume_carried(); int weight_capacity(bool real_life = true); @@ -208,7 +245,9 @@ class player { // ---------------VALUES----------------- int posx, posy; + bool in_vehicle; // Means player sit inside vehicle on the tile he is now player_activity activity; + player_activity backlog; // _missions vectors are of mission IDs std::vector active_missions; std::vector completed_missions; @@ -228,10 +267,11 @@ class player { int power_level, max_power_level; int hunger, thirst, fatigue, health; bool underwater; - bool can_dodge; int oxygen; unsigned int recoil; + unsigned int driving_recoil; unsigned int scent; + int dodges_left, blocks_left; int stim, pain, pkill, radiation; int cash; int moves; @@ -248,6 +288,8 @@ class player { inventory inv; itype_id last_item; std::vector worn; + std::vector styles; + itype_id style_selected; item weapon; item ret_null; // Null item, sometimes returns by weapon() etc diff --git a/pldata.h b/pldata.h index 9a978111e2..9f8e5cab7e 100644 --- a/pldata.h +++ b/pldata.h @@ -1,6 +1,8 @@ #ifndef _PLDATA_H_ #define _PLDATA_H_ +#include + enum character_type { PLTYPE_CUSTOM, PLTYPE_RANDOM, @@ -44,9 +46,16 @@ enum dis_type { DI_HALLU, DI_VISUALS, DI_IODINE, DI_TOOK_XANAX, DI_TOOK_PROZAC, DI_TOOK_FLUMED, DI_ADRENALINE, DI_ASTHMA, DI_METH, // Traps - DI_BEARTRAP, DI_IN_PIT, + DI_BEARTRAP, DI_IN_PIT, DI_STUNNED, DI_DOWNED, +// Martial Arts + DI_ATTACK_BOOST, DI_DAMAGE_BOOST, DI_DODGE_BOOST, DI_ARMOR_BOOST, + DI_SPEED_BOOST, DI_VIPER_COMBO, // Other - DI_AMIGARA, DI_TELEGLOW, DI_ATTENTION, DI_EVIL, DI_ASKED_TO_FOLLOW, + DI_AMIGARA, DI_TELEGLOW, DI_ATTENTION, DI_EVIL, +// Inflicted by an NPC + DI_ASKED_TO_FOLLOW, DI_ASKED_TO_LEAD, DI_ASKED_FOR_ITEM, +// NPC-only + DI_CATCH_UP }; enum add_type { @@ -58,9 +67,10 @@ enum add_type { struct disease { dis_type type; + int intensity; int duration; - disease() { type = DI_NULL; duration = 0; } - disease(dis_type t, int d) { type = t; duration = d;} + disease() { type = DI_NULL; duration = 0; intensity = 0; } + disease(dis_type t, int d, int i = 0) { type = t; duration = d; intensity = i;} }; struct addiction @@ -75,7 +85,8 @@ struct addiction enum activity_type { ACT_NULL = 0, - ACT_RELOAD, ACT_READ, ACT_WAIT, ACT_CRAFT, ACT_BUTCHER, ACT_BUILD, + ACT_RELOAD, ACT_READ, ACT_WAIT, ACT_CRAFT, ACT_BUTCHER, ACT_BUILD, ACT_VEHICLE, + ACT_TRAIN, NUM_ACTIVITIES }; @@ -86,8 +97,10 @@ struct player_activity int index; std::vector values; point placement; + player_activity() { type = ACT_NULL; moves_left = 0; index = -1; placement = point(-1, -1); } + player_activity(activity_type t, int turns, int Index) { type = t; @@ -95,6 +108,40 @@ struct player_activity index = Index; placement = point(-1, -1); } + + player_activity(player_activity ©) + { + type = copy.type; + moves_left = copy.moves_left; + index = copy.index; + placement = copy.placement; + values.clear(); + for (int i = 0; i < copy.values.size(); i++) + values.push_back(copy.values[i]); + } + + std::string save_info() + { + std::stringstream ret; + ret << type << " " << moves_left << " " << index << " " << placement.x << + " " << placement.y << " " << values.size(); + for (int i = 0; i < values.size(); i++) + ret << " " << values[i]; + + return ret.str(); + } + + void load_info(std::stringstream &dump) + { + int tmp, tmptype; + dump >> tmptype >> moves_left >> index >> placement.x >> placement.y >> tmp; + type = activity_type(tmptype); + for (int i = 0; i < tmp; i++) { + int tmp2; + dump >> tmp2; + values.push_back(tmp2); + } + } }; enum pl_flag { @@ -126,6 +173,7 @@ enum pl_flag { PF_HEARTLESS, // No morale penalty for murder &c PF_ANDROID, // Start with two bionics (occasionally one) PF_ROBUST, // Mutations tend to be good (usually they tend to be bad) + PF_MARTIAL_ARTS, // Start with a martial art PF_SPLIT, // Null trait, splits between bad & good @@ -364,6 +412,9 @@ You start the game with a power system, and one random bionic enhancement."}, {"Robust Genetics", 2, 0, 0, "\ You have a very strong genetic base. If you mutate, the odds that the\n\ mutation will be beneficial are greatly increased."}, +{"Martial Arts Training", 3, 0, 0, "\ +You have receives some martial arts training at a local dojo. You will start\n\ +with your choice of karate, judo, aikido, tai chi, or taekwando."}, {"NULL", 0, 0, 0, " -------------------------------------------------- "}, diff --git a/ranged.cpp b/ranged.cpp index 529ff26974..47edf1520c 100644 --- a/ranged.cpp +++ b/ranged.cpp @@ -85,17 +85,25 @@ void game::fire(player &p, int tarx, int tary, std::vector &trajectory, if (curshot > 0 && (mon_at(tarx, tary) == -1 || z[mon_at(tarx, tary)].hp <= 0)) { std::vector new_targets; + int mondex; for (int radius = 1; radius <= 2 + p.sklevel[sk_gun] && new_targets.empty(); radius++) { for (int diff = 0 - radius; diff <= radius; diff++) { - if (mon_at(tarx + diff, tary - radius) != -1) + mondex = mon_at(tarx + diff, tary - radius); + if (mondex != -1 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + diff, tary - radius) ); - if (mon_at(tarx + diff, tary + radius) != -1) + + mondex = mon_at(tarx + diff, tary + radius); + if (mondex != -1 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + diff, tary + radius) ); + if (diff != 0 - radius && diff != radius) { // Corners were already checked - if (mon_at(tarx - radius, tary + diff) != -1) + mondex = mon_at(tarx - radius, tary + diff); + if (mondex != -1 && z[mondex].friendly == 0) new_targets.push_back( point(tarx - radius, tary + diff) ); - if (mon_at(tarx + radius, tary + diff) != -1) + + mondex = mon_at(tarx + radius, tary + diff); + if (mondex != -1 && z[mondex].friendly == 0) new_targets.push_back( point(tarx + radius, tary + diff) ); } } @@ -155,7 +163,8 @@ void game::fire(player &p, int tarx, int tary, std::vector &trajectory, } } int dam = p.weapon.gun_damage(); - for (int i = 0; i < trajectory.size() && dam > 0; i++) { + for (int i = 0; i < trajectory.size() && + (dam > 0 || (flags & IF_AMMO_FLAME)); i++) { if (i > 0) m.drawsq(w_terrain, u, trajectory[i-1].x, trajectory[i-1].y, false, true); // Drawing the bullet uses player u, and not player p, because it's drawn @@ -248,7 +257,7 @@ void game::throw_item(player &p, int tarx, int tary, item &thrown, std::vector &trajectory) { int deviation = 0; - int trange = 1.5 * trig_dist(p.posx, p.posy, tarx, tary); + int trange = 1.5 * rl_dist(p.posx, p.posy, tarx, tary); if (p.sklevel[sk_throw] < 8) deviation += rng(0, 8 - p.sklevel[sk_throw]); @@ -353,7 +362,7 @@ void game::throw_item(player &p, int tarx, int tary, item &thrown, add_msg("%s You hit the %s for %d damage.", message.c_str(), z[mon_at(tx, ty)].name().c_str(), dam); else if (u_see(tx, ty, tart)) - add_msg("%s hits the %s for %d damage.", + add_msg("%s hits the %s for %d damage.", message.c_str(), z[mon_at(tx, ty)].name().c_str(), dam); if (z[mon_at(tx, ty)].hurt(dam)) kill_mon(mon_at(tx, ty)); @@ -409,7 +418,7 @@ std::vector game::target(int &x, int &y, int lowx, int lowy, int hix, double closest = -1; double dist; for (int i = 0; i < t.size(); i++) { - dist = trig_dist(t[i].posx, t[i].posy, u.posx, u.posy); + dist = rl_dist(t[i].posx, t[i].posy, u.posx, u.posy); if (closest < 0 || dist < closest) { closest = dist; target = i; @@ -424,6 +433,9 @@ std::vector game::target(int &x, int &y, int lowx, int lowy, int hix, WINDOW* w_target = newwin(13, 48, 12, SEEX * 2 + 8); wborder(w_target, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); + if (!relevent) // currently targetting vehicle to refill with fuel + mvwprintz(w_target, 1, 1, c_red, "Select a vehicle"); + else if (relevent == &u.weapon && relevent->is_gun()) mvwprintz(w_target, 1, 1, c_red, "Firing %s - %s (%d)", u.weapon.tname().c_str(), u.weapon.curammo->name.c_str(), @@ -432,6 +444,7 @@ std::vector game::target(int &x, int &y, int lowx, int lowy, int hix, mvwprintz(w_target, 1, 1, c_red, "Throwing %s", relevent->tname().c_str()); mvwprintz(w_target, 2, 1, c_white, "Move cursor to target with directional keys."); + if (relevent) mvwprintz(w_target, 3, 1, c_white, "'<' '>' Cycle targets; 'f' or '.' to fire."); wrefresh(w_target); @@ -476,7 +489,14 @@ std::vector game::target(int &x, int &y, int lowx, int lowy, int hix, } } } - mvwprintw(w_target, 5, 1, "Range: %d", rl_dist(u.posx, u.posy, x, y)); + if (!relevent) // currently targetting vehicle to refill with fuel + { + vehicle &veh = m.veh_at (x, y); + if (veh.type != veh_null) + mvwprintw(w_target, 5, 1, "There is a %s", veh.name.c_str()); + } + else + mvwprintw(w_target, 5, 1, "Range: %d", rl_dist(u.posx, u.posy, x, y)); if (mon_at(x, y) == -1) { mvwputch(w_terrain, y + SEEY - u.posy, x + SEEX - u.posx, c_red, '*'); mvwprintw(w_status, 0, 9, " "); @@ -683,7 +703,8 @@ double calculate_missed_by(player &p, int trange) deviation += rng(0, p.weapon.curammo->accuracy); deviation += rng(0, p.weapon.accuracy()); - deviation += rng(int(p.recoil / 4), p.recoil); + int adj_recoil = p.recoil + p.driving_recoil; + deviation += rng(int(adj_recoil / 4), adj_recoil); // .013 * trange is a computationally cheap version of finding the tangent. // (note that .00325 * 4 = .013; .00325 is used because deviation is a number diff --git a/setvector.cpp b/setvector.cpp index ab6f628bcb..7851d99d9d 100644 --- a/setvector.cpp +++ b/setvector.cpp @@ -117,3 +117,19 @@ void setvector(std::vector &vec, ... ) vec.push_back(tmp); va_end(ap); } + +void setvector(std::vector &vec, ... ) +{ + va_list ap; + va_start(ap, vec); + char *tmpname; + technique_id tmptech; + int tmplevel; + + while (tmpname = (char *)va_arg(ap, int)) { + tmptech = (technique_id)va_arg(ap, int); + tmplevel = (int)va_arg(ap, int); + vec.push_back( style_move(tmpname, tmptech, tmplevel) ); + } + va_end(ap); +} diff --git a/setvector.h b/setvector.h index fe277cfc6b..333fb98873 100644 --- a/setvector.h +++ b/setvector.h @@ -16,5 +16,6 @@ void setvector(std::vector &vec, ... ); void setvector(std::vector &vec, ... ); void setvector(std::vector &vec, ... ); void setvector(std::vector &vec, ... ); +void setvector(std::vector &vec, ... ); template void setvec(std::vector &vec, ... ); #endif diff --git a/skill.cpp b/skill.cpp index c959ac231e..57d6e46017 100644 --- a/skill.cpp +++ b/skill.cpp @@ -57,6 +57,8 @@ std::string skill_name(int sk) return "barter"; case sk_swimming: return "swimming"; + case sk_driving: + return "driving"; case num_skill_types: return "out of bounds"; } @@ -187,6 +189,10 @@ might even see you getting freebies."; return "\ Your skill at swimming. This affects speed, your ability to swim while\n\ wearing clothes or carrying weights, and in-water combat."; + case sk_driving: + return "\ +Your skill at driving. This affects how well you can control a vehicle,\n\ +as well as the penalty of shooting while driving."; case num_skill_types: return "out of bounds"; default: diff --git a/skill.h b/skill.h index d784c2f161..faf1e0e2ec 100644 --- a/skill.h +++ b/skill.h @@ -17,7 +17,7 @@ enum skill { // Social sk_speech, sk_barter, // Other - sk_computer, sk_survival, sk_traps, sk_swimming, + sk_computer, sk_survival, sk_traps, sk_swimming, sk_driving, num_skill_types // MUST be last! }; diff --git a/trap.h b/trap.h index 477f6fc75c..452923d34f 100644 --- a/trap.h +++ b/trap.h @@ -26,6 +26,7 @@ enum trap_id { tr_sinkhole, tr_pit, tr_spike_pit, + tr_lava, tr_portal, tr_ledge, tr_boobytrap, @@ -53,6 +54,7 @@ struct trapfunc { void sinkhole (game *g, int x, int y); void pit (game *g, int x, int y); void pit_spikes (game *g, int x, int y); + void lava (game *g, int x, int y); void portal (game *g, int x, int y) { }; void ledge (game *g, int x, int y); void boobytrap (game *g, int x, int y); @@ -77,6 +79,7 @@ struct trapfuncm { void sinkhole (game *g, monster *z, int x, int y) { }; void pit (game *g, monster *z, int x, int y); void pit_spikes(game *g, monster *z, int x, int y); + void lava (game *g, monster *z, int x, int y); void portal (game *g, monster *z, int x, int y) { }; void ledge (game *g, monster *z, int x, int y); void boobytrap (game *g, monster *z, int x, int y); diff --git a/trapdef.cpp b/trapdef.cpp index 68a89c0ee9..042a537df9 100644 --- a/trapdef.cpp +++ b/trapdef.cpp @@ -91,6 +91,10 @@ traps.push_back(new trap(id, sym, color, name, visibility, avoidance,\ &trapfunc::pit_spikes, &trapfuncm::pit_spikes, itm_null, NULL); + TRAP("lava", '~', c_red, 0, 99, 99, + &trapfunc::lava, &trapfuncm::lava, + itm_null, NULL); + // The '%' symbol makes the portal cycle through ~*0& // Name Symbol Color Vis Avd Diff TRAP("shimmering portal", '%', c_magenta, 0, 30, 99, diff --git a/trapfunc.cpp b/trapfunc.cpp index 54d9956895..d06a384aab 100644 --- a/trapfunc.cpp +++ b/trapfunc.cpp @@ -429,6 +429,40 @@ void trapfuncm::pit_spikes(game *g, monster *z, int x, int y) } } +void trapfunc::lava(game *g, int x, int y) +{ + g->add_msg("The %s burns you horribly!", g->m.tername(x, y).c_str()); + g->u.hit(g, bp_feet, 0, 0, 20); + g->u.hit(g, bp_feet, 1, 0, 20); + g->u.hit(g, bp_legs, 0, 0, 20); + g->u.hit(g, bp_legs, 1, 0, 20); +} + +void trapfuncm::lava(game *g, monster *z, int x, int y) +{ + int junk; + bool sees = g->u_see(z, junk); + if (sees) + g->add_msg("The %s burns the %s!", g->m.tername(x, y).c_str(), + z->name().c_str()); + + int dam = 30; + if (z->made_of(FLESH)) + dam = 50; + if (z->made_of(VEGGY)) + dam = 80; + if (z->made_of(PAPER) || z->made_of(LIQUID) || z->made_of(POWDER) || + z->made_of(WOOD) || z->made_of(COTTON) || z->made_of(WOOL)) + dam = 200; + if (z->made_of(STONE)) + dam = 15; + if (z->made_of(KEVLAR) || z->made_of(STEEL)) + dam = 5; + + z->hurt(dam); +} + + void trapfunc::sinkhole(game *g, int x, int y) { g->add_msg("You step into a sinkhole, and start to sink down!"); diff --git a/wish.cpp b/wish.cpp index 865020ba5f..27f3f0091f 100644 --- a/wish.cpp +++ b/wish.cpp @@ -127,7 +127,7 @@ void game::wish() shift++; if (shift + 23 > itypes.size()) shift = itypes.size() - 23; } - for (int i = 1; i < 24; i++) { + for (int i = 1; i < 24 && i-1+shift < itypes.size(); i++) { nc_color col = c_white; if (i == a + 1) col = h_white; From c4df7ad9be99cabe27dce1eeb520845f381a19bb Mon Sep 17 00:00:00 2001 From: Whales Date: Sat, 30 Jun 2012 19:34:14 -0400 Subject: [PATCH 31/51] Adding missing files, lol --- tileray.cpp | 295 ++++++++ tileray.h | 49 ++ veh_interact.cpp | 700 ++++++++++++++++++ veh_interact.h | 80 +++ veh_type.h | 266 +++++++ veh_typedef.cpp | 214 ++++++ vehicle.cpp | 1776 ++++++++++++++++++++++++++++++++++++++++++++++ vehicle.h | 334 +++++++++ 8 files changed, 3714 insertions(+) create mode 100644 tileray.cpp create mode 100644 tileray.h create mode 100644 veh_interact.cpp create mode 100644 veh_interact.h create mode 100644 veh_type.h create mode 100644 veh_typedef.cpp create mode 100644 vehicle.cpp create mode 100644 vehicle.h diff --git a/tileray.cpp b/tileray.cpp new file mode 100644 index 0000000000..ce50dcd747 --- /dev/null +++ b/tileray.cpp @@ -0,0 +1,295 @@ +#include "tileray.h" +#include +#include + +static const int sx[4] = { 1, -1, -1, 1 }; +static const int sy[4] = { 1, 1, -1, -1 }; + +tileray::tileray (): deltax(0), deltay(0), direction(0), leftover (0), + last_dx(0), last_dy(0), infinite (false), steps(0) +{ +} + +tileray::tileray (int adx, int ady) +{ + init (adx, ady); +} + +tileray::tileray (int adir): direction (adir) +{ + init (adir); +} + +void tileray::init (int adx, int ady) +{ + leftover = 0; + last_dx = 0; + last_dy = 0; + deltax = adx; + deltay = ady; + if (!adx && !ady) + direction = 0; + else + direction = (int) (atan2 (deltay, deltax) * 180.0 / M_PI); + infinite = false; + steps = 0; +} + +void tileray::init (int adir) +{ + leftover = 0; + direction = adir; + if (direction -0) + direction += 360; + if (direction >= 360) + direction -= 360; + last_dx = 0; + last_dy = 0; + deltax = abs((int) (cos ((float) direction * M_PI / 180.0) * 100)); + deltay = abs((int) (sin ((float) direction * M_PI / 180.0) * 100)); + infinite = true; + steps = 0; +} + +int tileray::dx () +{ + return last_dx; +} + +int tileray::dy () +{ + return last_dy; +} + +int tileray::dir () +{ + return direction; +} + +int tileray::dir4 () +{ + if (direction >= 45 && direction <= 135) + return 1; + else + if (direction > 135 && direction < 225) + return 2; + else + if (direction >= 225 && direction <= 315) + return 3; + else + return 0; +} + +char tileray::dir_symbol (char sym) +{ + switch (sym) + { + case 'j': + switch (dir4()) + { + default: + case 0: return 'h'; + case 1: return 'j'; + case 2: return 'h'; + case 3: return 'j'; + } + case 'h': + switch (dir4()) + { + default: + case 0: return 'j'; + case 1: return 'h'; + case 2: return 'j'; + case 3: return 'h'; + } + case 'y': + switch (dir4()) + { + default: + case 0: return 'u'; + case 1: return 'n'; + case 2: return 'b'; + case 3: return 'y'; + } + case 'u': + switch (dir4()) + { + default: + case 0: return 'n'; + case 1: return 'b'; + case 2: return 'y'; + case 3: return 'u'; + } + case 'n': + switch (dir4()) + { + default: + case 0: return 'b'; + case 1: return 'y'; + case 2: return 'u'; + case 3: return 'n'; + } + case 'b': + switch (dir4()) + { + default: + case 0: return 'y'; + case 1: return 'u'; + case 2: return 'n'; + case 3: return 'b'; + } + case '^': + switch (dir4()) + { + default: + case 0: return '>'; + case 1: return 'v'; + case 2: return '<'; + case 3: return '^'; + } + case '[': + switch (dir4()) + { + default: + case 0: return '-'; + case 1: return '['; + case 2: return '-'; + case 3: return '['; + } + case ']': + switch (dir4()) + { + default: + case 0: return '-'; + case 1: return ']'; + case 2: return '-'; + case 3: return ']'; + } + case '|': + switch (dir4()) + { + default: + case 0: return '-'; + case 1: return '|'; + case 2: return '-'; + case 3: return '|'; + } + case '-': + switch (dir4()) + { + default: + case 0: return '|'; + case 1: return '-'; + case 2: return '|'; + case 3: return '-'; + } + case '=': + switch (dir4()) + { + default: + case 0: return 'H'; + case 1: return '='; + case 2: return 'H'; + case 3: return '='; + } + case 'H': + switch (dir4()) + { + default: + case 0: return '='; + case 1: return 'H'; + case 2: return '='; + case 3: return 'H'; + } + case '\\': + switch (dir4()) + { + default: + case 0: return '/'; + case 1: return '\\'; + case 2: return '/'; + case 3: return '\\'; + } + case '/': + switch (dir4()) + { + default: + case 0: return '\\'; + case 1: return '/'; + case 2: return '\\'; + case 3: return '/'; + } + default:; + } + return sym; +} + +int tileray::ortho_dx (int od) +{ + int quadr = (direction / 90) % 4; + od *= -sy[quadr]; + return mostly_vertical()? od : 0; +} + +int tileray::ortho_dy (int od) +{ + int quadr = (direction / 90) % 4; + od *= sx[quadr]; + return mostly_vertical()? 0 : od; +} + +bool tileray::mostly_vertical () +{ + return abs(deltax) <= abs(deltay); +} + +void tileray::advance (int num) +{ + last_dx = last_dy = 0; + int ax = abs(deltax); + int ay = abs(deltay); + int anum = abs (num); + for (int i = 0; i < anum; i++) + { + if (mostly_vertical ()) + { // mostly vertical line + leftover += ax; + if (leftover >= ay) + { + last_dx++; + leftover -= ay; + } + last_dy++; + } + else + { // mostly horizontal line + leftover += ay; + if (leftover >= ax) + { + last_dy++; + leftover -= ax; + } + last_dx++; + } + steps++; + } + + // offset calculated for 0-90 deg quadrant, we need to adjust if direction is other + int quadr = (direction / 90) % 4; + last_dx *= sx[quadr]; + last_dy *= sy[quadr]; + if (num < 0) + { + last_dx = -last_dx; + last_dy = -last_dy; + } +} + +bool tileray::end () +{ + if (infinite) + return true; + return mostly_vertical()? steps >= abs(deltay) - 1 : steps >= abs(deltax) - 1; +} + + diff --git a/tileray.h b/tileray.h new file mode 100644 index 0000000000..e9cac2d863 --- /dev/null +++ b/tileray.h @@ -0,0 +1,49 @@ +#ifndef _TILERAY_H_ +#define _TILERAY_H_ + +// Class for calculating tile coords +// of a point that moves along the ray with given +// direction (dir) or delta tile coords (dx, dy). +// Advance method will move to the next tile +// along ray +// Direction is angle in degrees from positive x-axis to positive y-axis: +// +// | 270 orthogonal dir left (-) +// 180 | 0 ^ +// ----+----> X -------> X (forward dir, 0 degrees) +// | v +// V 90 orthogonal dir right (+) +// Y +class tileray +{ +private: + int deltax; // ray delta x + int deltay; // ray delta y + int leftover; // counter to shift coords + int direction; // ray direction + int last_dx; // dx of last advance + int last_dy; // dy of last advance + int steps; // how many steps we advanced so far + bool infinite; // ray is infinite (end will always return true) +public: + tileray (); + tileray (int adx, int ady); + tileray (int adir); + + void init (int adx, int ady); // init ray with dx,dy + void init (int adir); // init ray with direction + + int dx (); // return dx of last advance (-1 to 1) + int dy (); // return dy of last advance (-1 to 1) + int dir (); // return direction of ray (degrees) + int dir4 (); // return 4-sided direction (0 = east, 1 = south, 2 = west, 3 = north) + char dir_symbol (char sym); // convert certain symbols from north-facing variant into current dir facing + int ortho_dx (int od); // return dx for point at "od" distance in orthogonal direction + int ortho_dy (int od); // return dy for point at "od" distance in orthogonal direction + bool mostly_vertical (); // return if ray is mostly vertical + + void advance (int num = 1); // move to the next tile (calculate last dx, dy) + bool end (); // do we reach the end of (dx,dy) defined ray? +}; + +#endif diff --git a/veh_interact.cpp b/veh_interact.cpp new file mode 100644 index 0000000000..f6151ad63d --- /dev/null +++ b/veh_interact.cpp @@ -0,0 +1,700 @@ +#include +#include "veh_interact.h" +#include "vehicle.h" +#include "keypress.h" +#include "game.h" +#include "output.h" +#include "crafting.h" + + +veh_interact::veh_interact () +{ + cx = 0; + cy = 0; + cpart = -1; + ddx = 0; + ddy = 0; + sel_cmd = ' '; +} + +void veh_interact::exec (game *gm, vehicle *v, int x, int y) +{ + g = gm; + veh = v; + ex = x; + ey = y; + // x1 x2 + // y1 ----+------+-- + // | | + // y2 ----+------+ + // | + // | + winw1 = 12; + winw2 = 35; + winh1 = 3; + winh2 = 12; + winw12 = winw1 + winw2 + 1; + winw3 = 80 - winw1 - winw2 - 2; + winh3 = 25 - winh1 - winh2 - 2; + winh23 = winh2 + winh3 + 1; + winx1 = winw1; + winx2 = winw1 + winw2 + 1; + winy1 = winh1; + winy2 = winh1 + winh2 + 1; + + page_size = winh23; + // h w y x + w_grid = newwin(25, 80, 0, 0); + w_mode = newwin(1, 80, 0, 0); + w_msg = newwin(winh1 - 1, 80, 1, 0); + w_disp = newwin(winh2, winw1, winy1 + 1, 0); + w_parts = newwin(winh2, winw2, winy1 + 1, winx1 + 1); + w_stats = newwin(winh3, winw12, winy2 + 1, 0); + w_list = newwin(winh23, winw3, winy1 + 1, winx2 + 1); + + for (int i = 0; i < 25; i++) + { + mvwputch(w_grid, i, winx2, c_ltgray, i == winy1 || i == winy2? LINE_XOXX : LINE_XOXO); + if (i >= winy1 && i < winy2) + mvwputch(w_grid, i, winx1, c_ltgray, LINE_XOXO); + } + for (int i = 0; i < 80; i++) + { + mvwputch(w_grid, winy1, i, c_ltgray, i == winx1? LINE_OXXX : (i == winx2? LINE_OXXX : LINE_OXOX)); + if (i < winx2) + mvwputch(w_grid, winy2, i, c_ltgray, i == winx1? LINE_XXOX : LINE_OXOX); + } + wrefresh(w_grid); + + crafting_inv.form_from_map(g, point(g->u.posx, g->u.posy), PICKUP_RANGE); + crafting_inv += g->u.inv; + crafting_inv += g->u.weapon; + if (g->u.has_bionic(bio_tools)) + { + item tools(g->itypes[itm_toolset], g->turn); + tools.charges = g->u.power_level; + crafting_inv += tools; + } + int charges = ((it_tool *) g->itypes[itm_welder])->charges_per_use; + has_wrench = crafting_inv.has_amount(itm_wrench, 1) || + crafting_inv.has_amount(itm_toolset, 1); + has_hacksaw = crafting_inv.has_amount(itm_hacksaw, 1) || + crafting_inv.has_amount(itm_toolset, 1); + has_welder = (crafting_inv.has_amount(itm_welder, 1) && + crafting_inv.has_charges(itm_welder, charges)) || + (crafting_inv.has_amount(itm_toolset, 1) && + crafting_inv.has_charges(itm_toolset, charges/5)); + + display_stats (); + display_veh (); + move_cursor (0, 0); + bool finish = false; + while (!finish) + { + char ch = input(); // See keypress.h + int dx, dy; + get_direction (dx, dy, ch); + if (ch == KEY_ESCAPE) + finish = true; + else + if (dx != -2 && (dx || dy) && + cx + dx >= -6 && cx + dx < 6 && + cy + dy >= -6 && cy + dy < 6) + move_cursor(dx, dy); + else + { + int mval = cant_do(ch); + display_mode (ch); + switch (ch) + { + case 'i': do_install(mval); break; + case 'r': do_repair(mval); break; + case 'f': do_refill(mval); break; + case 'o': do_remove(mval); break; + default:; + } + if (sel_cmd != ' ') + finish = true; + display_mode (' '); + } + } + werase(w_grid); + werase(w_mode); + werase(w_msg); + werase(w_disp); + werase(w_parts); + werase(w_stats); + werase(w_list); + delwin(w_grid); + delwin(w_mode); + delwin(w_msg); + delwin(w_disp); + delwin(w_parts); + delwin(w_stats); + delwin(w_list); + erase(); +} + +int veh_interact::cant_do (char mode) +{ + switch (mode) + { + case 'i': // install mode + return can_mount.size() > 0? (!has_wrench || !has_welder? 2 : 0) : 1; + case 'r': // repair mode + return need_repair.size() > 0 && cpart >= 0? (!has_welder? 2 : 0) : 1; + case 'f': // refill mode + return ptank >= 0? (!has_fuel? 2 : 0) : 1; + case 'o': // remove mode + return cpart < 0? 1 : + (parts_here.size() < 2 && !veh->can_unmount(cpart)? 2 : + (!has_wrench || !has_hacksaw? 3 : + (g->u.sklevel[sk_mechanics] < 2? 4 : 0))); + default: + return -1; + } +} + +void veh_interact::do_install(int reason) +{ + werase (w_msg); + if (g->u.morale_level() < MIN_MORALE_CRAFT) + { // See morale.h + mvwprintz(w_msg, 0, 1, c_ltred, "Your morale is too low to construct..."); + wrefresh (w_msg); + return; + } + switch (reason) + { + case 1: + mvwprintz(w_msg, 0, 1, c_ltred, "Cannot install any part here."); + wrefresh (w_msg); + return; + case 2: + mvwprintz(w_msg, 0, 1, c_ltgray, "You need a wrench and a powered welder to install parts."); + mvwprintz(w_msg, 0, 12, has_wrench? c_ltgreen : c_red, "wrench"); + mvwprintz(w_msg, 0, 25, has_welder? c_ltgreen : c_red, "powered welder"); + wrefresh (w_msg); + return; + default:; + } + mvwprintz(w_mode, 0, 1, c_ltgray, "Choose new part to install here:"); + wrefresh (w_mode); + int pos = 0; + int engines = 0; + int dif_eng = 0; + for (int p = 0; p < veh->parts.size(); p++) + if (veh->part_flag (p, vpf_engine)) + { + engines++; + dif_eng = dif_eng / 2 + 12; + } + while (true) + { + sel_part = can_mount[pos]; + display_list (pos); + itype_id itm = vpart_list[sel_part].item; + bool has_comps = crafting_inv.has_amount(itm, 1); + bool has_skill = g->u.sklevel[sk_mechanics] >= vpart_list[sel_part].difficulty; + werase (w_msg); + int slen = g->itypes[itm]->name.length(); + mvwprintz(w_msg, 0, 1, c_ltgray, "Needs %s and level %d skill in mechanics.", + g->itypes[itm]->name.c_str(), vpart_list[sel_part].difficulty); + mvwprintz(w_msg, 0, 7, has_comps? c_ltgreen : c_red, g->itypes[itm]->name.c_str()); + mvwprintz(w_msg, 0, 18+slen, has_skill? c_ltgreen : c_red, "%d", vpart_list[sel_part].difficulty); + bool eng = vpart_list[sel_part].flags & mfb (vpf_engine); + bool has_skill2 = !eng || (g->u.sklevel[sk_mechanics] >= dif_eng); + if (engines && eng) // already has engine + { + mvwprintz(w_msg, 1, 1, c_ltgray, + "You also need level %d skill in mechanics to install additional engine.", + dif_eng); + mvwprintz(w_msg, 1, 21, has_skill2? c_ltgreen : c_red, "%d", dif_eng); + } + wrefresh (w_msg); + char ch = input(); // See keypress.h + int dx, dy; + get_direction (dx, dy, ch); + if ((ch == '\n' || ch == ' ') && has_comps && has_skill && has_skill2) + { + sel_cmd = 'i'; + return; + } + else + if (ch == KEY_ESCAPE) + { + werase (w_list); + wrefresh (w_list); + werase (w_msg); + break; + } + if (dy == -1 || dy == 1) + { + pos += dy; + if (pos < 0) + pos = can_mount.size()-1; + else + if (pos >= can_mount.size()) + pos = 0; + } + } +} + +void veh_interact::do_repair(int reason) +{ + werase (w_msg); + if (g->u.morale_level() < MIN_MORALE_CRAFT) + { // See morale.h + mvwprintz(w_msg, 0, 1, c_ltred, "Your morale is too low to construct..."); + wrefresh (w_msg); + return; + } + switch (reason) + { + case 1: + mvwprintz(w_msg, 0, 1, c_ltred, "There are no damaged parts here."); + wrefresh (w_msg); + return; + case 2: + mvwprintz(w_msg, 0, 1, c_ltgray, "You need a powered welder to repair."); + mvwprintz(w_msg, 0, 12, has_welder? c_ltgreen : c_red, "powered welder"); + wrefresh (w_msg); + return; + default:; + } + mvwprintz(w_mode, 0, 1, c_ltgray, "Choose a part here to repair:"); + wrefresh (w_mode); + int pos = 0; + while (true) + { + sel_part = parts_here[need_repair[pos]]; + werase (w_parts); + veh->print_part_desc (w_parts, 0, winw2, cpart, need_repair[pos]); + wrefresh (w_parts); + werase (w_msg); + bool has_comps = true; + int dif = veh->part_info(sel_part).difficulty + (veh->parts[sel_part].hp <= 0? 0 : 2); + bool has_skill = dif <= g->u.sklevel[sk_mechanics]; + mvwprintz(w_msg, 0, 1, c_ltgray, "You need level %d skill in mechanics.", dif); + mvwprintz(w_msg, 0, 16, has_skill? c_ltgreen : c_red, "%d", dif); + if (veh->parts[sel_part].hp <= 0) + { + itype_id itm = veh->part_info(sel_part).item; + has_comps = crafting_inv.has_amount(itm, 1); + mvwprintz(w_msg, 1, 1, c_ltgray, "You also need a wrench and %s to replace broken one.", + g->itypes[itm]->name.c_str()); + mvwprintz(w_msg, 1, 17, has_wrench? c_ltgreen : c_red, "wrench"); + mvwprintz(w_msg, 1, 28, has_comps? c_ltgreen : c_red, g->itypes[itm]->name.c_str()); + } + wrefresh (w_msg); + char ch = input(); // See keypress.h + int dx, dy; + get_direction (dx, dy, ch); + if ((ch == '\n' || ch == ' ') && has_comps && (veh->parts[sel_part].hp > 0 || has_wrench) && has_skill) + { + sel_cmd = 'r'; + return; + } + else + if (ch == KEY_ESCAPE) + { + werase (w_parts); + veh->print_part_desc (w_parts, 0, winw2, cpart, -1); + wrefresh (w_parts); + werase (w_msg); + break; + } + if (dy == -1 || dy == 1) + { + pos += dy; + if (pos < 0) + pos = need_repair.size()-1; + else + if (pos >= need_repair.size()) + pos = 0; + } + } +} + +void veh_interact::do_refill(int reason) +{ + werase (w_msg); + switch (reason) + { + case 1: + mvwprintz(w_msg, 0, 1, c_ltred, "There's no fuel tank here."); + wrefresh (w_msg); + return; + case 2: + mvwprintz(w_msg, 0, 1, c_ltgray, "You need %s.", veh->fuel_name(veh->part_info(ptank).fuel_type).c_str()); + mvwprintz(w_msg, 0, 10, c_red, veh->fuel_name(veh->part_info(ptank).fuel_type).c_str()); + wrefresh (w_msg); + return; + default:; + } + sel_cmd = 'f'; + sel_part = ptank; +} + +void veh_interact::do_remove(int reason) +{ + werase (w_msg); + if (g->u.morale_level() < MIN_MORALE_CRAFT) + { // See morale.h + mvwprintz(w_msg, 0, 1, c_ltred, "Your morale is too low to construct..."); + wrefresh (w_msg); + return; + } + switch (reason) + { + case 1: + mvwprintz(w_msg, 0, 1, c_ltred, "No parts here."); + wrefresh (w_msg); + return; + case 2: + mvwprintz(w_msg, 0, 1, c_ltred, "You cannot remove mount point while something is attached to it."); + wrefresh (w_msg); + return; + case 3: + mvwprintz(w_msg, 0, 1, c_ltgray, "You need a wrench and a hack saw to remove parts."); + mvwprintz(w_msg, 0, 12, has_wrench? c_ltgreen : c_red, "wrench"); + mvwprintz(w_msg, 0, 25, has_hacksaw? c_ltgreen : c_red, "hack saw"); + wrefresh (w_msg); + return; + case 4: + mvwprintz(w_msg, 0, 1, c_ltred, "You need level 2 mechanics skill to remove parts."); + wrefresh (w_msg); + return; + default:; + } + mvwprintz(w_mode, 0, 1, c_ltgray, "Choose a part here to remove:"); + wrefresh (w_mode); + int first = parts_here.size() > 1? 1 : 0; + int pos = first; + while (true) + { + sel_part = parts_here[pos]; + werase (w_parts); + veh->print_part_desc (w_parts, 0, winw2, cpart, pos); + wrefresh (w_parts); + char ch = input(); // See keypress.h + int dx, dy; + get_direction (dx, dy, ch); + if (ch == '\n' || ch == ' ') + { + sel_cmd = 'o'; + return; + } + else + if (ch == KEY_ESCAPE) + { + werase (w_parts); + veh->print_part_desc (w_parts, 0, winw2, cpart, -1); + wrefresh (w_parts); + werase (w_msg); + break; + } + if (dy == -1 || dy == 1) + { + pos += dy; + if (pos < first) + pos = parts_here.size()-1; + else + if (pos >= parts_here.size()) + pos = first; + } + } +} + +int veh_interact::part_at (int dx, int dy) +{ + int vdx = -ddx - dy; + int vdy = dx - ddy; + for (int ep = 0; ep < veh->external_parts.size(); ep++) + { + int p = veh->external_parts[ep]; + if (veh->parts[p].mount_dx == vdx && veh->parts[p].mount_dy == vdy) + return p; + } + return -1; +} + +void veh_interact::move_cursor (int dx, int dy) +{ + mvwputch (w_disp, cy+6, cx+6, cpart >= 0? veh->part_color (cpart) : c_black, special_symbol(cpart >= 0? veh->part_sym (cpart) : ' ')); + cx += dx; + cy += dy; + cpart = part_at (cx, cy); + int vdx = -ddx - cy; + int vdy = cx - ddy; + int vx, vy; + veh->coord_translate (vdx, vdy, vx, vy); + int vehx = veh->global_x() + vx; + int vehy = veh->global_y() + vy; + bool obstruct = g->m.move_cost_ter_only (vehx, vehy) == 0; + vehicle &oveh = g->m.veh_at (vehx, vehy); + if (oveh.type != veh_null && &oveh != veh) + obstruct = true; + nc_color col = cpart >= 0? veh->part_color (cpart) : c_black; + mvwputch (w_disp, cy+6, cx+6, obstruct? red_background(col) : hilite(col), special_symbol(cpart >= 0? veh->part_sym (cpart) : ' ')); + wrefresh (w_disp); + werase (w_parts); + veh->print_part_desc (w_parts, 0, winw2, cpart, -1); + wrefresh (w_parts); + + can_mount.clear(); + has_mats.clear(); + if (!obstruct) + for (int i = 1; i < num_vparts; i++) + { + if (veh->can_mount (vdx, vdy, (vpart_id) i)) + can_mount.push_back (i); + } + need_repair.clear(); + parts_here.clear(); + ptank = -1; + if (cpart >= 0) + { + parts_here = veh->internal_parts(cpart); + parts_here.insert (parts_here.begin(), cpart); + for (int i = 0; i < parts_here.size(); i++) + { + int p = parts_here[i]; + if (veh->parts[p].hp < veh->part_info(p).durability) + need_repair.push_back (i); + if (veh->part_flag(p, vpf_fuel_tank) && veh->parts[p].amount < veh->part_info(p).size) + ptank = p; + } + } + has_fuel = ptank >= 0? g->pl_refill_vehicle(*veh, ptank, true) : false; + werase (w_msg); + wrefresh (w_msg); + display_mode (' '); +} + +void veh_interact::display_veh () +{ + int x1 = 12, y1 = 12, x2 = -12, y2 = -12; + for (int ep = 0; ep < veh->external_parts.size(); ep++) + { + int p = veh->external_parts[ep]; + if (veh->parts[p].mount_dx < x1) + x1 = veh->parts[p].mount_dx; + if (veh->parts[p].mount_dy < y1) + y1 = veh->parts[p].mount_dy; + if (veh->parts[p].mount_dx > x2) + x2 = veh->parts[p].mount_dx; + if (veh->parts[p].mount_dy > y2) + y2 = veh->parts[p].mount_dy; + } + ddx = 0; + ddy = 0; + if (x2 - x1 < 11) { x1--; x2++; } + if (y2 - y1 < 11 ) { y1--; y2++; } + if (x1 < -5) + ddx = -5 - x1; + else + if (x2 > 6) + ddx = 6 - x2; + if (y1 < -6) + ddy = -6 - y1; + else + if (y2 > 5) + ddy = 5 - y2; + + for (int ep = 0; ep < veh->external_parts.size(); ep++) + { + int p = veh->external_parts[ep]; + char sym = veh->part_sym (p); + nc_color col = veh->part_color (p); + int y = -(veh->parts[p].mount_dx + ddx); + int x = veh->parts[p].mount_dy + ddy; + mvwputch (w_disp, 6+y, 6+x, cx == x && cy == y? hilite(col) : col, special_symbol(sym)); + if (cx == x && cy == y) + cpart = p; + } + wrefresh (w_disp); +} + +void veh_interact::display_stats () +{ + bool conf = veh->valid_wheel_config(); + mvwprintz(w_stats, 0, 1, c_ltgray, "Name: "); + mvwprintz(w_stats, 0, 7, c_ltgreen, veh->name.c_str()); + mvwprintz(w_stats, 1, 1, c_ltgray, "Safe speed: mph"); + mvwprintz(w_stats, 1, 14, c_ltgreen,"%3d", veh->safe_velocity(false) / 100); + mvwprintz(w_stats, 2, 1, c_ltgray, "Top speed: mph"); + mvwprintz(w_stats, 2, 14, c_ltred, "%3d", veh->max_velocity(false) / 100); + mvwprintz(w_stats, 3, 1, c_ltgray, "Accel.: mph/t"); + mvwprintz(w_stats, 3, 14, c_ltblue,"%3d", veh->acceleration(false) / 100); + mvwprintz(w_stats, 4, 1, c_ltgray, "Mass: kg"); + mvwprintz(w_stats, 4, 12, c_ltblue,"%5d", (int) (veh->total_mass() / 4 * 0.45)); + mvwprintz(w_stats, 1, 26, c_ltgray, "K dynamics: "); + mvwprintz(w_stats, 1, 37, c_ltblue, "%3d", (int) (veh->k_dynamics() * 100)); + mvwputch (w_stats, 1, 41, c_ltgray, '%'); + mvwprintz(w_stats, 2, 26, c_ltgray, "K mass: "); + mvwprintz(w_stats, 2, 37, c_ltblue, "%3d", (int) (veh->k_mass() * 100)); + mvwputch (w_stats, 2, 41, c_ltgray, '%'); + mvwprintz(w_stats, 5, 1, c_ltgray, "Wheels: "); + mvwprintz(w_stats, 5, 11, conf? c_ltgreen : c_ltred, conf? "enough" : " lack"); + mvwprintz(w_stats, 6, 1, c_ltgray, "Fuel usage (safe): "); + int xfu = 20; + int ftypes[3] = { AT_GAS, AT_BATT, AT_PLASMA }; + nc_color fcs[3] = { c_ltred, c_yellow, c_ltblue }; + bool first = true; + for (int i = 0; i < 3; i++) + { + int fu = veh->basic_consumption (ftypes[i]); + if (fu > 0) + { + fu = fu / 100; + if (fu < 1) + fu = 1; + if (!first) + mvwprintz(w_stats, 6, xfu++, c_ltgray, "/"); + mvwprintz(w_stats, 6, xfu++, fcs[i], "%d", fu); + if (fu > 9) xfu++; + if (fu > 99) xfu++; + first = false; + } + } + veh->print_fuel_indicator (w_stats, 0, 42); + wrefresh (w_stats); +} + +void veh_interact::display_mode (char mode) +{ + werase (w_mode); + if (mode == ' ') + { + bool mi = !cant_do('i'); + bool mr = !cant_do('r'); + bool mf = !cant_do('f'); + bool mo = !cant_do('o'); + mvwprintz(w_mode, 0, 1, mi? c_ltgray : c_dkgray, "install"); + mvwputch (w_mode, 0, 1, mi? c_ltgreen : c_green, 'i'); + mvwprintz(w_mode, 0, 9, mr? c_ltgray : c_dkgray, "repair"); + mvwputch (w_mode, 0, 9, mr? c_ltgreen : c_green, 'r'); + mvwprintz(w_mode, 0, 16, mf? c_ltgray : c_dkgray, "refill"); + mvwputch (w_mode, 0, 18, mf? c_ltgreen : c_green, 'f'); + mvwprintz(w_mode, 0, 23, mo? c_ltgray : c_dkgray, "remove"); + mvwputch (w_mode, 0, 26, mo? c_ltgreen : c_green, 'o'); + } + mvwprintz(w_mode, 0, 71, c_ltgreen, "ESC"); + mvwprintz(w_mode, 0, 74, c_ltgray, "-back"); + wrefresh (w_mode); +} + +void veh_interact::display_list (int pos) +{ + werase (w_list); + int page = pos / page_size; + for (int i = page * page_size; i < (page + 1) * page_size && i < can_mount.size(); i++) + { + int y = i - page * page_size; + itype_id itm = vpart_list[can_mount[i]].item; + bool has_comps = crafting_inv.has_amount(itm, 1); + bool has_skill = g->u.sklevel[sk_mechanics] >= vpart_list[can_mount[i]].difficulty; + nc_color col = has_comps && has_skill? c_white : c_dkgray; + mvwprintz(w_list, y, 3, pos == i? hilite (col) : col, vpart_list[can_mount[i]].name); + mvwputch (w_list, y, 1, + vpart_list[can_mount[i]].color, special_symbol (vpart_list[can_mount[i]].sym)); + } + wrefresh (w_list); +} + +void complete_vehicle (game *g) +{ + if (g->u.activity.values.size() < 7) + { + debugmsg ("Invalid activity ACT_VEHICLE values:%d", g->u.activity.values.size()); + return; + } + vehicle &veh = g->m.veh_at (g->u.activity.values[0], g->u.activity.values[1]); + if (veh.type == veh_null) + { + debugmsg ("Activity ACT_VEHICLE: vehicle not found"); + return; + } + char cmd = (char) g->u.activity.index; + int dx = g->u.activity.values[4]; + int dy = g->u.activity.values[5]; + int part = g->u.activity.values[6]; + std::vector comps; + std::vector tools; + int welder_charges = ((it_tool *) g->itypes[itm_welder])->charges_per_use; + if (veh.type == veh_null) + { + debugmsg ("Activity ACT_VEHICLE: bad part %d/%d", part, veh.parts.size()); + return; + } + itype_id itm; + bool broken; + + int dd = 2; + switch (cmd) + { + case 'i': + if (veh.install_part (dx, dy, (vpart_id) part) < 0) + debugmsg ("complete_vehicle install part fails dx=%d dy=%d id=%d", dx, dy, part); + comps.push_back(component(vpart_list[part].item, 1)); + consume_items(g, comps); + tools.push_back(component(itm_welder, welder_charges)); + tools.push_back(component(itm_toolset, welder_charges/5)); + consume_tools(g, tools); + g->add_msg ("You install %s into %s.", + vpart_list[part].name, veh.name.c_str()); + g->u.practice (sk_mechanics, vpart_list[part].difficulty * 5 + 20); + break; + case 'r': + if (veh.parts[part].hp <= 0) + { + comps.push_back(component(veh.part_info(part).item, 1)); + consume_items(g, comps); + tools.push_back(component(itm_wrench, 1)); + consume_tools(g, tools); + tools.clear(); + dd = 0; + veh.insides_dirty = true; + } + tools.push_back(component(itm_welder, welder_charges)); + tools.push_back(component(itm_toolset, welder_charges/5)); + consume_tools(g, tools); + veh.parts[part].hp = veh.part_info(part).durability; + g->add_msg ("You repair %s's %s.", + veh.name.c_str(), veh.part_info(part).name); + g->u.practice (sk_mechanics, (vpart_list[part].difficulty + dd) * 5 + 20); + break; + case 'f': + if (!g->pl_refill_vehicle(veh, part, true)) + debugmsg ("complete_vehicle refill broken"); + g->pl_refill_vehicle(veh, part); + break; + case 'o': + for (int i = 0; i < veh.parts[part].items.size(); i++) + g->m.add_item (g->u.posx, g->u.posy, veh.parts[part].items[i]); + veh.parts[part].items.clear(); + itm = veh.part_info(part).item; + broken = veh.parts[part].hp <= 0; + if (veh.parts.size() < 2) + { + g->add_msg ("You completely dismantle %s.", veh.name.c_str()); + g->u.activity.type = ACT_NULL; + g->m.destroy_vehicle (veh); + } + else + { + g->add_msg ("You remove %s%s from %s.", broken? "broken " : "", veh.part_info(part).name, veh.name.c_str()); + veh.remove_part (part); + } + if (!broken) + g->m.add_item (g->u.posx, g->u.posy, g->itypes[itm], g->turn); + g->u.practice (sk_mechanics, 2 * 5 + 20); + break; + default:; + + } +} + + + diff --git a/veh_interact.h b/veh_interact.h new file mode 100644 index 0000000000..0ddd6d7b5d --- /dev/null +++ b/veh_interact.h @@ -0,0 +1,80 @@ +#ifndef _VEH_INTERACT_H_ +#define _VEH_INTERACT_H_ + +#include +#include "output.h" +#include "inventory.h" + +class vehicle; +class game; + +class veh_interact +{ +public: + int cx; + int cy; + int ddx; + int ddy; + int sel_part; + char sel_cmd; +private: + int cpart; + int page_size; + WINDOW *w_grid; + WINDOW *w_mode; + WINDOW *w_msg; + WINDOW *w_disp; + WINDOW *w_parts; + WINDOW *w_stats; + WINDOW *w_list; + + int winw1; + int winw2; + int winh1; + int winh2; + int winw12; + int winw3; + int winh3; + int winh23; + int winx1; + int winx2; + int winy1; + int winy2; + + vehicle *veh; + game *g; + int ex, ey; + bool has_wrench; + bool has_welder; + bool has_hacksaw; + inventory crafting_inv; + + int part_at (int dx, int dy); + void move_cursor (int dx, int dy); + int cant_do (char mode); + + void do_install(int reason); + void do_repair(int reason); + void do_refill(int reason); + void do_remove(int reason); + + void display_veh (); + void display_stats (); + void display_mode (char mode); + void display_list (int pos); + + std::vector can_mount; + std::vector has_mats; + std::vector need_repair; + std::vector parts_here; + int ptank; + bool obstruct; + bool has_fuel; +public: + veh_interact (); + void exec (game *gm, vehicle *v, int x, int y); +}; + +void complete_vehicle (game *g); + +#endif diff --git a/veh_type.h b/veh_type.h new file mode 100644 index 0000000000..02caefbee1 --- /dev/null +++ b/veh_type.h @@ -0,0 +1,266 @@ +#ifndef _VEH_TYPE_H_ +#define _VEH_TYPE_H_ + +#include "color.h" +#include "itype.h" + +#ifndef mfb +#define mfb(n) long(1 << (n)) +#endif + +enum vpart_id +{ + vp_null = 0, + +// external parts + vp_seat, + vp_frame_h, + vp_frame_v, + vp_frame_c, + vp_frame_y, + vp_frame_u, + vp_frame_n, + vp_frame_b, + vp_frame_h2, + vp_frame_v2, + vp_frame_cover, + vp_frame_handle, + vp_board_h, + vp_board_v, + vp_board_y, + vp_board_u, + vp_board_n, + vp_board_b, + vp_roof, + vp_door, + vp_window, + vp_blade_h, + vp_blade_v, + vp_spike_h, + vp_spike_v = vp_spike_h, + vp_wheel_large, + vp_wheel, + + vp_engine_gas_small, + vp_engine_gas_med, + vp_engine_gas_large, + vp_engine_motor, + vp_engine_motor_large, + vp_engine_plasma, + vp_fuel_tank_gas, + vp_fuel_tank_batt, + vp_fuel_tank_plut, + vp_fuel_tank_hydrogen, + vp_cargo_trunk, // over + vp_cargo_box, // over + +// pure internal parts + vp_controls, + vp_muffler, + vp_seatbelt, + vp_solar_panel, + vp_m249, + vp_flamethrower, + vp_plasmagun, + +// plating -- special case. mounted as internal, work as first line +// of defence and gives color to external part + vp_steel_plate, + vp_superalloy_plate, + vp_spiked_plate, + vp_hard_plate, + + num_vparts +}; + +enum vpart_flags +{ + vpf_external, // can be mounted as external part + vpf_internal, // can be mounted inside other part + vpf_mount_point, // allows mounting other parts to it + vpf_mount_inner, // allows mounting internal parts inside it (or over it) + vpf_mount_over, // allows mounting parts like cargo trunk over it + vpf_opaque, // can't see through it + vpf_obstacle, // can't pass through it + vpf_openable, // can open/close it + vpf_no_reinforce, // can't reinforce this part with armor plates + vpf_sharp, // cutting damage instead of bashing + vpf_unmount_on_damage, // when damaged, part is unmounted, rather than broken + +// functional flags (only one of each can be mounted per tile) + vpf_over, // can be mounted over other part + vpf_roof, // is a roof (cover) + vpf_wheel, // this part touches ground (trigger traps) + vpf_seat, // is seat + vpf_engine, // is engine + vpf_fuel_tank, // is fuel tank + vpf_cargo, // is cargo + vpf_controls, // is controls + vpf_muffler, // is muffler + vpf_seatbelt, // is seatbelt + vpf_solar_panel, // is solar panel + vpf_turret, // is turret + vpf_armor, // is armor plating + vpf_func_begin = vpf_over, + vpf_func_end = vpf_armor, + + num_vpflags +}; + +struct vpart_info +{ + const char *name; // part name + char sym; // symbol of part as if it's looking north + nc_color color; // color + char sym_broken; // symbol of broken part as if it's looking north + nc_color color_broken; // color of broken part + int dmg_mod; // damage modifier, percent + int durability; // durability + union + { + int par1; + int power; // engine (top spd), solar panel (% of 1 fuel per turn, can be > 100) + int size; // wheel, fuel tank, trunk + int bonus; // seatbelt (str), muffler (%) + }; + union + { + int par2; + int fuel_type; // engine, fuel tank + }; + itype_id item; // corresponding item + int difficulty; // installation difficulty (mechanics requirement) + unsigned long flags; // flags +}; + +// following symbols will be translated: +// y, u, n, b to NW, NE, SE, SW lines correspondingly +// h, j, c to horizontal, vertical, cross correspondingly +const vpart_info vpart_list[num_vparts] = +{ // name sym color sym_b color_b dmg dur par1 par2 item + { "null part", '?', c_red, '?', c_red, 100, 100, 0, 0, itm_null, 0, + 0 }, + { "seat", '#', c_red, '*', c_red, 60, 300, 0, 0, itm_seat, 1, + mfb(vpf_over) | mfb(vpf_seat) | mfb(vpf_no_reinforce) }, + { "frame", 'h', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'j', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'c', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'y', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'u', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'n', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'b', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", '=', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", 'H', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "frame", '^', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "handle", '^', c_ltcyan, '#', c_ltcyan, 100, 300, 0, 0, itm_frame, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, + { "board", 'h', c_ltgray, '#', c_ltgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, + { "board", 'j', c_ltgray, '#', c_ltgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, + { "board", 'y', c_ltgray, '#', c_ltgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, + { "board", 'u', c_ltgray, '#', c_ltgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, + { "board", 'n', c_ltgray, '#', c_ltgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, + { "board", 'b', c_ltgray, '#', c_ltgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, + { "roof", '#', c_ltgray, '#', c_dkgray, 100, 1000, 0, 0, itm_steel_plate, 1, + mfb(vpf_internal) | mfb(vpf_roof) }, + { "door", '+', c_cyan, '&', c_cyan, 80, 200, 0, 0, itm_frame, 2, + mfb(vpf_external) | mfb(vpf_obstacle) | mfb(vpf_openable) }, + { "windshield", '"', c_ltcyan, '0', c_ltgray, 70, 50, 0, 0, itm_glass_sheet, 1, + mfb(vpf_over) | mfb(vpf_obstacle) | mfb(vpf_no_reinforce) }, + { "blade", '-', c_white, 'x', c_white, 250, 100, 0, 0, itm_machete, 3, + mfb(vpf_external) | mfb(vpf_unmount_on_damage) | mfb(vpf_sharp) | mfb(vpf_no_reinforce) }, + { "blade", '|', c_white, 'x', c_white, 350, 100, 0, 0, itm_machete, 3, + mfb(vpf_external) | mfb(vpf_unmount_on_damage) | mfb(vpf_sharp) | mfb(vpf_no_reinforce) }, + { "spike", '.', c_white, 'x', c_white, 300, 100, 0, 0, itm_spear_knife, 2, + mfb(vpf_external) | mfb(vpf_unmount_on_damage) | mfb(vpf_sharp) | mfb(vpf_no_reinforce) }, + +// size + { "large wheel",'0', c_dkgray, 'x', c_ltgray, 50, 300, 30, 0, itm_big_wheel, 3, + mfb(vpf_external) | mfb (vpf_mount_over) | mfb(vpf_wheel) | mfb(vpf_mount_point) }, + { "wheel", 'o', c_dkgray, 'x', c_ltgray, 50, 200, 10, 0, itm_wheel, 2, + mfb(vpf_external) | mfb (vpf_mount_over) | mfb(vpf_wheel) | mfb(vpf_mount_point) }, +// power type + { "1L combustion engine", '*', c_ltred, '#', c_red, 80, 200, 120, AT_GAS, itm_combustion_small, 3, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "2.5L combustion engine", '*', c_ltred, '#', c_red, 80, 300, 300, AT_GAS, itm_combustion, 4, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "6L combustion engine", '*', c_ltred, '#', c_red, 80, 400, 800, AT_GAS, itm_combustion_large, 5, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "electric motor", '*', c_yellow, '#', c_red, 80, 200, 70, AT_BATT, itm_motor, 3, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "large electric motor", '*', c_yellow, '#', c_red, 80, 400, 350, AT_BATT, itm_motor_large, 4, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "plasma engine", '*', c_ltblue, '#', c_red, 80, 250, 400, AT_PLASMA, itm_plasma_engine, 8, + mfb(vpf_internal) | mfb(vpf_engine) }, +// capacity type + { "gasoline tank", 'O', c_ltred, '#', c_red, 80, 150, 3000, AT_GAS, itm_metal_tank, 1, + mfb(vpf_internal) | mfb(vpf_fuel_tank) }, + { "storage battery", 'O', c_yellow, '#', c_red, 80, 300, 1000, AT_BATT, itm_storage_battery, 2, + mfb(vpf_internal) | mfb(vpf_fuel_tank) }, + { "minireactor", 'O', c_ltgreen, '#', c_red, 80, 700, 10000, AT_PLUT, itm_minireactor, 7, + mfb(vpf_internal) | mfb(vpf_fuel_tank) }, + { "hydrogene tank", 'O', c_ltblue, '#', c_red, 80, 150, 3000, AT_PLASMA, itm_metal_tank, 1, + mfb(vpf_internal) | mfb(vpf_fuel_tank) }, + { "trunk", 'H', c_brown, '#', c_brown, 80, 300, 400, 0, itm_frame, 1, + mfb(vpf_over) | mfb(vpf_cargo) }, + { "box", 'o', c_brown, '#', c_brown, 60, 100, 400, 0, itm_frame, 1, + mfb(vpf_over) | mfb(vpf_cargo) }, + + { "controls", '$', c_ltgray, '$', c_red, 10, 250, 0, 0, itm_vehicle_controls, 3, + mfb(vpf_internal) | mfb(vpf_controls) }, +// bonus + { "muffler", '/', c_ltgray, '/', c_ltgray, 10, 150, 40, 0, itm_muffler, 2, + mfb(vpf_internal) | mfb(vpf_muffler) }, + { "seatbelt", ',', c_ltgray, ',', c_red, 10, 200, 25, 0, itm_rope_6, 1, + mfb(vpf_internal) | mfb(vpf_seatbelt) }, + { "solar panel", '#', c_yellow, 'x', c_yellow, 10, 20, 30, 0, itm_solar_panel, 6, + mfb(vpf_over) | mfb(vpf_solar_panel) }, + + { "mounted M249", 't', c_cyan, '#', c_cyan, 80, 400, 0, AT_223, itm_m249, 7, + mfb(vpf_over) | mfb(vpf_turret) | mfb(vpf_cargo) }, + { "mounted flamethrower", 't', c_dkgray, '#', c_dkgray, 80, 400, 0, AT_GAS, itm_flamethrower, 8, + mfb(vpf_over) | mfb(vpf_turret) }, + { "mounted plasma gun", 't', c_ltblue, '#', c_ltblue, 80, 400, 0, AT_PLASMA, itm_plasma_rifle, 9, + mfb(vpf_over) | mfb(vpf_turret) }, + + { "steel plating", ')', c_ltcyan, ')', c_ltcyan, 100, 1000, 0, 0, itm_steel_plate, 3, + mfb(vpf_internal) | mfb(vpf_armor) }, + { "superalloy plating",')', c_dkgray, ')', c_dkgray, 100, 900, 0, 0, itm_alloy_plate, 5, + mfb(vpf_internal) | mfb(vpf_armor) }, + { "spiked plating", ')', c_red, ')', c_red, 150, 900, 0, 0, itm_spiked_plate, 3, + mfb(vpf_internal) | mfb(vpf_armor) | mfb(vpf_sharp) }, + { "hard plating", ')', c_cyan, ')', c_cyan, 100, 2300, 0, 0, itm_hard_plate, 5, + mfb(vpf_internal) | mfb(vpf_armor) } +}; + + +enum vhtype_id +{ + veh_null = 0, + veh_custom, + +// in-built vehicles + veh_motorcycle, + veh_sandbike, + veh_car, + veh_truck, + + num_vehicles +}; + +#endif diff --git a/veh_typedef.cpp b/veh_typedef.cpp new file mode 100644 index 0000000000..ce286576e0 --- /dev/null +++ b/veh_typedef.cpp @@ -0,0 +1,214 @@ +#include "vehicle.h" +#include "game.h" + +// GENERAL GUIDELINES +// When adding a new vehicle, you MUST REMEMBER to insert it in the vhtype_id enum +// at the bottom of veh_type.h! +// also, before using PART, you MUST call VEHICLE +// +// To determine mount position for parts (dx, dy), check this scheme: +// orthogonal dir left: (Y-) +// ^ +// back: X- -------> forward dir: X+ +// v +// orthogonal dir right (Y+) +// +// i.e, if you want to add a part to the back from the center of vehicle, +// use dx = -1, dy = 0; +// for the part 1 tile forward and two tiles left from the center of vehicle, +// use dx = 1, dy = -2. +// +// Internal parts should be added after external on the same mount point, i.e: +// PART (0, 1, vp_seat); // put a seat (it's external) +// PART (0, 1, vp_controls); // put controls for driver here +// PART (0, 1, vp_seatbelt); // also, put a seatbelt here +// To determine, what parts can be external, and what can not, check +// vpart_id enum in veh_type.h file +// If you use wrong config, installation of part will fail + +void game::init_vehicles() +{ + vehicle *veh; + int index = 0; + int pi; + vtypes.push_back(new vehicle(this, (vhtype_id)index++)); // veh_null + vtypes.push_back(new vehicle(this, (vhtype_id)index++)); // veh_custom + +#define VEHICLE(nm) { veh = new vehicle(this, (vhtype_id)index++); veh->name = nm; vtypes.push_back(veh); } +#define PART(mdx, mdy, id) { pi = veh->install_part(mdx, mdy, id); \ + if (pi < 0) debugmsg("init_vehicles: '%s' part '%s'(%d) can't be installed to %d,%d", veh->name.c_str(), vpart_list[id].name, veh->parts.size(), mdx, mdy); } + + // name + VEHICLE ("motorcycle"); + // o + // ^ + // # + // o + + // dx, dy, part_id + PART (0, 0, vp_frame_v2); + PART (0, 0, vp_seat); + PART (0, 0, vp_controls); + PART (0, 0, vp_engine_gas_small); + PART (1, 0, vp_frame_handle); + PART (1, 0, vp_fuel_tank_gas); + PART (2, 0, vp_wheel); + PART (-1, 0, vp_wheel); + PART (-1, 0, vp_cargo_box); + + // name + VEHICLE ("quad bike"); + // 0^0 + // # + // 0H0 + + // dx, dy, part_id + PART (0, 0, vp_frame_v2); + PART (0, 0, vp_seat); + PART (0, 0, vp_controls); + PART (0, 0, vp_seatbelt); + PART (1, 0, vp_frame_cover); + PART (1, 0, vp_engine_gas_med); + PART (1, 0, vp_fuel_tank_gas); + PART (1, 0, vp_steel_plate); + PART (-1,0, vp_frame_h); +// PART (-1,0, vp_engine_motor); +// PART (-1,0, vp_fuel_tank_plut); + PART (-1,0, vp_cargo_trunk); + PART (-1,0, vp_steel_plate); + PART (1, -1, vp_wheel_large); + PART (1, 1, vp_wheel_large); + PART (-1,-1, vp_wheel_large); + PART (-1, 1, vp_wheel_large); +// PART (1, -2, vp_blade_h); +// PART (1, 2, vp_blade_h); + + // name + VEHICLE ("car"); + // o--o + // |""| + // +##+ + // +##+ + // |HH| + // o++o + + // dx, dy, part_id + PART (0, 0, vp_frame_v2); + PART (0, 0, vp_seat); + PART (0, 0, vp_seatbelt); + PART (0, 0, vp_controls); + PART (0, 0, vp_roof); + PART (0, 1, vp_frame_v2); + PART (0, 1, vp_seat); + PART (0, 1, vp_seatbelt); + PART (0, 1, vp_roof); + PART (0, -1, vp_door); + PART (0, 2, vp_door); + PART (-1, 0, vp_frame_v2); + PART (-1, 0, vp_seat); + PART (-1, 0, vp_seatbelt); + PART (-1, 0, vp_roof); + PART (-1, 1, vp_frame_v2); + PART (-1, 1, vp_seat); + PART (-1, 1, vp_seatbelt); + PART (-1, 1, vp_roof); + PART (-1, -1, vp_door); + PART (-1, 2, vp_door); + PART (1, 0, vp_frame_h); + PART (1, 0, vp_window); + PART (1, 1, vp_frame_h); + PART (1, 1, vp_window); + PART (1, -1, vp_frame_v); + PART (1, 2, vp_frame_v); + PART (2, 0, vp_frame_h); + PART (2, 0, vp_engine_gas_med); + PART (2, 1, vp_frame_h); + PART (2, -1, vp_wheel); + PART (2, 2, vp_wheel); + PART (-2, 0, vp_frame_v); + PART (-2, 0, vp_cargo_trunk); + PART (-2, 0, vp_muffler); + PART (-2, 0, vp_roof); + PART (-2, 1, vp_frame_v); + PART (-2, 1, vp_cargo_trunk); + PART (-2, 1, vp_roof); + PART (-2, -1, vp_board_v); + PART (-2, -1, vp_fuel_tank_gas); + PART (-2, 2, vp_board_v); + PART (-3, -1, vp_wheel); + PART (-3, 0, vp_door); + PART (-3, 1, vp_door); + PART (-3, 2, vp_wheel); + + + // name + VEHICLE ("truck"); + // 0-^-0 + // |"""| + // +###+ + // |---| + // |HHH| + // 0HHH0 + + PART (0, 0, vp_frame_v); + PART (0, 0, vp_cargo_box); + PART (0, 0, vp_roof); +// PART (0, 0, vp_seatbelt); + PART (0, -1, vp_frame_v2); + PART (0, -1, vp_seat); + PART (0, -1, vp_seatbelt); + PART (0, -1, vp_roof); + PART (0, 1, vp_frame_v2); + PART (0, 1, vp_seat); + PART (0, 1, vp_seatbelt); + PART (0, 1, vp_roof); + PART (0, -2, vp_door); + PART (0, 2, vp_door); + PART (0, -1, vp_controls); + + PART (1, 0, vp_frame_h); + PART (1, 0, vp_window); + PART (1, -1, vp_frame_h); + PART (1, -1, vp_window); + PART (1, 1, vp_frame_h); + PART (1, 1, vp_window); + PART (1, -2, vp_frame_v); + PART (1, 2, vp_frame_v); + + PART (2, -1, vp_frame_h); + PART (2, 0, vp_frame_cover); + PART (2, 0, vp_engine_gas_med); + PART (2, 1, vp_frame_h); + PART (2, -2, vp_wheel_large); + PART (2, 2, vp_wheel_large); + + PART (-1, -1, vp_board_h); + PART (-1, 0, vp_board_h); + PART (-1, 1, vp_board_h); + PART (-1, -2, vp_board_b); + PART (-1, -2, vp_fuel_tank_gas); + PART (-1, 2, vp_board_n); + PART (-1, 2, vp_fuel_tank_gas); + + PART (-2, -1, vp_frame_v); + PART (-2, -1, vp_cargo_trunk); + PART (-2, 0, vp_frame_v); + PART (-2, 0, vp_cargo_trunk); + PART (-2, 1, vp_frame_v); + PART (-2, 1, vp_cargo_trunk); + PART (-2, -2, vp_board_v); + PART (-2, 2, vp_board_v); + + PART (-3, -1, vp_frame_h); + PART (-3, -1, vp_cargo_trunk); + PART (-3, 0, vp_frame_h); + PART (-3, 0, vp_cargo_trunk); + PART (-3, 1, vp_frame_h); + PART (-3, 1, vp_cargo_trunk); + PART (-3, -2, vp_wheel_large); + PART (-3, 2, vp_wheel_large); + + if (vtypes.size() != num_vehicles) + debugmsg("%d vehicles, %d types", vtypes.size(), num_vehicles); +} + diff --git a/vehicle.cpp b/vehicle.cpp new file mode 100644 index 0000000000..4f790a4685 --- /dev/null +++ b/vehicle.cpp @@ -0,0 +1,1776 @@ +#include "vehicle.h" +#include "map.h" +#include "output.h" +#include "game.h" +#include "item.h" +#include +#include +#if (defined _WIN32 || defined WINDOWS) + #include "catacurse.h" +#else + #include +#endif + +vehicle::vehicle(game *ag, vhtype_id type_id): g(ag), type(type_id) +{ + posx = 0; + posy = 0; + velocity = 0; + turn_dir = 0; + last_turn = 0; + moves = 0; + turret_mode = 0; + cruise_velocity = 0; + skidding = false; + cruise_on = true; + insides_dirty = true; + if (type >= num_vehicles) + type = 0; + if (type > veh_custom) + { // get a copy of sample vehicle of this type + if (type < g->vtypes.size()) + { + *this = *(g->vtypes[type]); + init_state(); + } + } + precalc_mounts(0, face.dir()); +} + +vehicle::~vehicle() +{ +} + +bool vehicle::player_in_control (player *p) +{ + if (type == veh_null) + return false; + int veh_part; + vehicle &veh = g->m.veh_at (p->posx, p->posy, veh_part); + if (&veh != this) + return false; + return part_with_feature(veh_part, vpf_controls, false) >= 0 && p->in_vehicle; +} + +void vehicle::load (std::ifstream &stin) +{ + int t; + int fdir, mdir, skd, prts, cr_on; + stin >> + t >> + posx >> + posy >> + fdir >> + mdir >> + turn_dir >> + velocity >> + cruise_velocity >> + cr_on >> + turret_mode >> + skd >> + moves >> + prts; + type = (vhtype_id) t; + face.init (fdir); + move.init (mdir); + skidding = skd != 0; + cruise_on = cr_on != 0; + std::string databuff; + getline(stin, databuff); // Clear EoL + getline(stin, name); // read name + int itms = 0; + for (int p = 0; p < prts; p++) + { + int pid, pdx, pdy, php, pam, pbld, pnit; + stin >> pid >> pdx >> pdy >> php >> pam >> pbld >> pnit; + getline(stin, databuff); // Clear EoL + vehicle_part new_part; + new_part.id = (vpart_id) pid; + new_part.mount_dx = pdx; + new_part.mount_dy = pdy; + new_part.hp = php; + new_part.blood = pbld; + new_part.amount = pam; + for (int j = 0; j < pnit; j++) + { + itms++; + getline(stin, databuff); + item itm; + itm.load_info (databuff, g); + new_part.items.push_back (itm); + int ncont; + stin >> ncont; // how many items inside container + getline(stin, databuff); // Clear EoL + for (int k = 0; k < ncont; k++) + { + getline(stin, databuff); + item citm; + citm.load_info (databuff, g); + new_part.items[new_part.items.size()-1].put_in (citm); + } + } + parts.push_back (new_part); + } + find_external_parts (); + find_exhaust (); + insides_dirty = true; + precalc_mounts (0, face.dir()); +} + +void vehicle::save (std::ofstream &stout) +{ + stout << + int(type) << " " << + posx << " " << + posy << " " << + face.dir() << " " << + move.dir() << " " << + turn_dir << " " << + velocity << " " << + cruise_velocity << " " << + (cruise_on? 1 : 0) << " " << + turret_mode << " " << + (skidding? 1 : 0) << " " << + moves << " " << + parts.size() << std::endl; + stout << name << std::endl; + + for (int p = 0; p < parts.size(); p++) + { + stout << + parts[p].id << " " << + parts[p].mount_dx << " " << + parts[p].mount_dy << " " << + parts[p].hp << " " << + parts[p].amount << " " << + parts[p].blood << " " << + parts[p].items.size() << std::endl; + for (int i = 0; i < parts[p].items.size(); i++) + { + stout << parts[p].items[i].save_info() << std::endl; // item info + stout << parts[p].items[i].contents.size() << std::endl; // how many items inside this item + for (int l = 0; l < parts[p].items[i].contents.size(); l++) + stout << parts[p].items[i].contents[l].save_info() << std::endl; // contents info + } + } +} + +void vehicle::init_state() +{ + for (int p = 0; p < parts.size(); p++) + { + if (part_flag(p, vpf_fuel_tank)) // 10% to 75% fuel for tank + parts[p].amount = rng (part_info(p).size / 10, part_info(p).size * 3 / 4); + if (part_flag(p, vpf_openable)) // doors are closed + parts[p].open = 0; + if (part_flag(p, vpf_seat)) // no passengers + parts[p].passenger = 0; + } +} + +const vpart_info& vehicle::part_info (int index) +{ + vpart_id id = vp_null; + if (index < 0 || index >= parts.size()) + id = vp_null; + else + id = parts[index].id; + if (id < vp_null || id >= num_vparts) + id = vp_null; + return vpart_list[id]; +} + +bool vehicle::can_mount (int dx, int dy, vpart_id id) +{ + if (id <= 0 || id >= num_vparts) + return false; + bool n3ar = parts.size() < 1 || (vpart_list[id].flags & mfb(vpf_internal)) + || (vpart_list[id].flags & mfb(vpf_over)); // first and internal parts needs no mount point + if (!n3ar) + for (int i = 0; i < 4; i++) + { + int ndx = i < 2? (i == 0? -1 : 1) : 0; + int ndy = i < 2? 0 : (i == 2? - 1: 1); + std::vector parts_n3ar = parts_at_relative (dx + ndx, dy + ndy); + if (parts_n3ar.size() < 1) + continue; + if (part_flag(parts_n3ar[0], vpf_mount_point)) + { + n3ar = true; + break; + } + } + if (!n3ar) + { +// debugmsg ("can_mount%d(%d,%d): no point to mount", id, dx, dy); + return false; // no point to mount + } + +// TODO: seatbelts must require an obstacle part n3arby + + std::vector parts_here = parts_at_relative (dx, dy); + if (parts_here.size() < 1) + { + int res = vpart_list[id].flags & mfb(vpf_external); +// if (!res) +// debugmsg ("can_mount: not first or external"); + return vpart_list[id].flags & mfb(vpf_external); // can be mounted if first and external + } + + int flags1 = part_info(parts_here[0]).flags; + if ((vpart_list[id].flags & mfb(vpf_armor)) && flags1 & mfb(vpf_no_reinforce)) + { +// debugmsg ("can_mount: armor plates on non-reinforcable part"); + return false; // trying to put armor plates on non-reinforcable part + } + + for (int vf = vpf_func_begin; vf <= vpf_func_end; vf++) + if ((vpart_list[id].flags & mfb(vf)) && part_with_feature(parts_here[0], vf, false) >= 0) + { +// debugmsg ("can_mount%d(%d,%d): already has inner part with same unique feature",id, dx, dy); + return false; // this part already has inner part with same unique feature + } + + bool allow_inner = flags1 & mfb(vpf_mount_inner); + bool allow_over = flags1 & mfb(vpf_mount_over); + bool this_inner = vpart_list[id].flags & mfb(vpf_internal); + bool this_over = (vpart_list[id].flags & mfb(vpf_over)) || (vpart_list[id].flags & mfb(vpf_armor)); + if (allow_inner && (this_inner || this_over)) + return true; // can mount as internal part or over it + if (allow_over && this_over) + return true; // can mount as part over +// debugmsg ("can_mount%d(%d,%d): allow_i=%c allow_o=%c this_i=%c this_o=%c", id, dx, dy, +// allow_inner? 'y' : 'n', +// allow_over? 'y' : 'n', +// this_inner? 'y' : 'n', +// this_over? 'y' : 'n'); + return false; +} + +bool vehicle::can_unmount (int p) +{ + int dx = parts[p].mount_dx; + int dy = parts[p].mount_dy; + if (!dx && !dy) + { // central point + bool is_ext = false; + for (int ep = 0; ep < external_parts.size(); ep++) + if (external_parts[ep] == p) + { + is_ext = true; + break; + } + if (external_parts.size() > 1 && is_ext) + return false; // unmounting 0, 0 part anly allowed as last part + } + + if (!part_flag (p, vpf_mount_point)) + return true; + for (int i = 0; i < 4; i++) + { + int ndx = i < 2? (i == 0? -1 : 1) : 0; + int ndy = i < 2? 0 : (i == 2? - 1: 1); + if (!(dx + ndx) && !(dy + ndy)) + continue; // 0, 0 point is main mount + if (parts_at_relative (dx + ndx, dy + ndy).size() > 0) + { + int cnt = 0; + for (int j = 0; j < 4; j++) + { + int jdx = j < 2? (j == 0? -1 : 1) : 0; + int jdy = j < 2? 0 : (j == 2? - 1: 1); + std::vector pc = parts_at_relative (dx + ndx + jdx, dy + ndy + jdy); + if (pc.size() > 0 && part_with_feature (pc[0], vpf_mount_point) >= 0) + cnt++; + } + if (cnt < 2) + return false; + } + } + return true; +} + +int vehicle::install_part (int dx, int dy, vpart_id id, int hp, bool force) +{ + if (!force && !can_mount (dx, dy, id)) + return -1; // no money -- no ski! + // if this is first part, add this part to list of external parts + if (parts_at_relative (dx, dy).size () < 1) + external_parts.push_back (parts.size()); + vehicle_part new_part; + new_part.id = id; + new_part.mount_dx = dx; + new_part.mount_dy = dy; + new_part.hp = hp < 0? vpart_list[id].durability : hp; + new_part.amount = 0; + new_part.blood = 0; + parts.push_back (new_part); + find_exhaust (); + precalc_mounts (0, face.dir()); + insides_dirty = true; + return parts.size() - 1; +} + +void vehicle::remove_part (int p) +{ + parts.erase(parts.begin() + p); + find_external_parts (); + find_exhaust (); + precalc_mounts (0, face.dir()); + insides_dirty = true; +} + + +std::vector vehicle::parts_at_relative (int dx, int dy) +{ + std::vector res; + for (int i = 0; i < parts.size(); i++) + if (parts[i].mount_dx == dx && parts[i].mount_dy == dy) + res.push_back (i); + return res; +} + +std::vector vehicle::internal_parts (int p) +{ + std::vector res; + for (int i = p + 1; i < parts.size(); i++) + if (parts[i].mount_dx == parts[p].mount_dx && parts[i].mount_dy == parts[p].mount_dy) + res.push_back (i); + return res; +} + +int vehicle::part_with_feature (int p, unsigned int f, bool unbroken) +{ + if (part_flag(p, f)) + return p; + std::vector parts_here = internal_parts (p); + for (int i = 0; i < parts_here.size(); i++) + if (part_flag(parts_here[i], f) && (!unbroken || parts[parts_here[i]].hp > 0)) + return parts_here[i]; + return -1; +} + +bool vehicle::part_flag (int p, unsigned int f) +{ + if (p < 0 || p >= parts.size()) + return false; + return (bool) (part_info(p).flags & mfb (f)); +} + +int vehicle::part_at(int dx, int dy) +{ + for (int i = 0; i < external_parts.size(); i++) + { + int p = external_parts[i]; + if (parts[p].precalc_dx[0] == dx && + parts[p].precalc_dy[0] == dy) + return p; + } + return -1; +} + +char vehicle::part_sym (int p) +{ + if (p < 0 || p >= parts.size()) + return 0; + std::vector ph = internal_parts (p); + int po = part_with_feature(p, vpf_over, false); + int pd = po < 0? p : po; + if (part_flag (pd, vpf_openable) && parts[pd].open) + return '\''; // open door + return parts[pd].hp <= 0? part_info(pd).sym_broken : part_info(pd).sym; +} + +nc_color vehicle::part_color (int p) +{ + if (p < 0 || p >= parts.size()) + return c_black; + int parm = part_with_feature(p, vpf_armor, false); + int po = part_with_feature(p, vpf_over, false); + int pd = po < 0? p : po; + if (parts[p].blood > 200) + return c_red; + else + if (parts[p].blood > 0) + return c_ltred; + + if (parts[pd].hp <= 0) + return part_info(pd).color_broken; + + // first, check if there's a part over. then, if armor here (projects its color on part) + if (po >= 0) + return part_info(po).color; + else + if (parm >= 0) + return part_info(parm).color; + + return part_info(pd).color; +} + +void vehicle::print_part_desc (void *w, int y1, int width, int p, int hl) +{ + WINDOW *win = (WINDOW *) w; + if (p < 0 || p >= parts.size()) + return; + std::vector pl = internal_parts (p); + pl.insert (pl.begin(), p); + int y = y1; + for (int i = 0; i < pl.size(); i++) + { + int dur = part_info (pl[i]).durability; + int per_cond = parts[pl[i]].hp * 100 / (dur < 1? 1 : dur); + nc_color col_cond = c_dkgray; + if (parts[pl[i]].hp >= dur) + col_cond = c_green; + else + if (per_cond >= 80) + col_cond = c_ltgreen; + else + if (per_cond >= 50) + col_cond = c_yellow; + else + if (per_cond >= 20) + col_cond = c_ltred; + else + if (parts[pl[i]].hp > 0) + col_cond = c_red; + + bool armor = part_flag(pl[i], vpf_armor); + mvwprintz(win, y, 2, i == hl? hilite(col_cond) : col_cond, part_info(pl[i]).name); + mvwprintz(win, y, 1, i == hl? hilite(c_ltgray) : c_ltgray, armor? "(" : (i? "-" : "[")); + mvwprintz(win, y, 2 + strlen(part_info(pl[i]).name), i == hl? hilite(c_ltgray) : c_ltgray, armor? ")" : (i? "-" : "]")); +// mvwprintz(win, y, 3 + strlen(part_info(pl[i]).name), c_ltred, "%d", parts[pl[i]].blood); + + if (i == 0) + mvwprintz(win, y, width-5, c_ltgray, is_inside(pl[i])? " In " : "Out "); + y++; + } +} + +void vehicle::print_fuel_indicator (void *w, int y, int x) +{ + WINDOW *win = (WINDOW *) w; + const nc_color fcs[num_fuel_types] = { c_ltred, c_yellow, c_ltgreen, c_ltblue }; + const char fsyms[5] = { 'E', '\\', '|', '/', 'F' }; + nc_color col_indf1 = c_ltgray; + mvwprintz(win, y, x, col_indf1, "E...F"); + for (int i = 0; i < num_fuel_types; i++) + { + int cap = fuel_capacity(fuel_types[i]); + if (cap > 0) + { + int amnt = cap > 0? fuel_left(fuel_types[i]) * 99 / cap : 0; + int indf = (amnt / 20) % 5; + mvwprintz(win, y, x + indf, fcs[i], "%c", fsyms[indf]); + } + } +} + +void vehicle::coord_translate (int reldx, int reldy, int &dx, int &dy) +{ + tileray tdir (face.dir()); + tdir.advance (reldx); + dx = tdir.dx() + tdir.ortho_dx(reldy); + dy = tdir.dy() + tdir.ortho_dy(reldy); +} + +void vehicle::coord_translate (int dir, int reldx, int reldy, int &dx, int &dy) +{ + tileray tdir (dir); + tdir.advance (reldx); + dx = tdir.dx() + tdir.ortho_dx(reldy); + dy = tdir.dy() + tdir.ortho_dy(reldy); +} + +void vehicle::precalc_mounts (int idir, int dir) +{ + if (idir < 0 || idir > 1) + idir = 0; + int ps = parts.size(); + for (int p = 0; p < parts.size(); p++) + { + int dx, dy; + coord_translate (dir, parts[p].mount_dx, parts[p].mount_dy, dx, dy); + parts[p].precalc_dx[idir] = dx; + parts[p].precalc_dy[idir] = dy; + } +} + +std::vector vehicle::boarded_parts() +{ + std::vector res; + for (int p = 0; p < parts.size(); p++) + if (part_flag (p, vpf_seat) && parts[p].passenger) + res.push_back (p); + return res; +} + +player *vehicle::get_passenger (int p) +{ + p = part_with_feature (p, vpf_seat, false); + if (p >= 0 && parts[p].passenger) + { + int x = global_x () + parts[p].precalc_dx[0]; + int y = global_y () + parts[p].precalc_dy[0]; + if (g->u.posx == x && g->u.posy == y && g->u.in_vehicle) + { + return &g->u; + } + int npcdex = g->npc_at (x, y); + if (npcdex >= 0) + { + return &g->active_npc[npcdex]; + } + } + return 0; +} + +int vehicle::global_x () +{ + return smx * SEEX + posx; +} + +int vehicle::global_y () +{ + return smy * SEEY + posy; +} + +int vehicle::total_mass () +{ + int m = 0; + for (int i = 0; i < parts.size(); i++) + { + m += g->itypes[part_info(i).item]->weight; + for (int j = 0; j < parts[i].items.size(); j++) + m += parts[i].items[j].type->weight; + if (part_flag(i,vpf_seat) && parts[i].passenger) + m += 520; // TODO: get real weight + } + return m; +} + +int vehicle::fuel_left (int ftype, bool for_engine) +{ + int fl = 0; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_fuel_tank) && + (ftype == part_info(p).fuel_type || + (for_engine && ftype == AT_BATT && part_info(p).fuel_type == AT_PLUT))) + fl += parts[p].amount; + return fl; +} + +int vehicle::fuel_capacity (int ftype) +{ + int cap = 0; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_fuel_tank) && ftype == part_info(p).fuel_type) + cap += part_info(p).size; + return cap; +} + +int vehicle::refill (int ftype, int amount) +{ + for (int p = 0; p < parts.size(); p++) + { + if (part_flag(p, vpf_fuel_tank) && + part_info(p).fuel_type == ftype && + parts[p].amount < part_info(p).size) + { + int need = part_info(p).size - parts[p].amount; + if (amount < need) + { + parts[p].amount += amount; + return 0; + } + else + { + parts[p].amount += need; + amount -= need; + } + } + } + return amount; +} + +std::string vehicle::fuel_name(int ftype) +{ + switch (ftype) + { + case AT_GAS: + return std::string("gasoline"); + case AT_BATT: + return std::string("batteries"); + case AT_PLUT: + return std::string("plutonium cells"); + case AT_PLASMA: + return std::string("hydrogen"); + default: + return std::string("INVALID FUEL (BUG)"); + } +} + +int vehicle::basic_consumption (int ftype) +{ + if (ftype == AT_PLUT) + ftype = AT_BATT; + int cnt = 0; + int fcon = 0; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_engine) && + ftype == part_info(p).fuel_type && + parts[p].hp > 0) + { + fcon += part_info(p).power; + cnt++; + } + if (fcon < 100 && cnt > 0) + fcon = 100; + return fcon; +} + +int vehicle::total_power (bool fueled) +{ + int pwr = 0; + int cnt = 0; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_engine) && + (fuel_left (part_info(p).fuel_type, true) || !fueled) && + parts[p].hp > 0) + { + pwr += part_info(p).power; + cnt++; + } + if (cnt > 1) + pwr = pwr * 4 / (4 + cnt -1); + return pwr; +} + +int vehicle::solar_power () +{ + int pwr = 0; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_solar_panel) && parts[p].hp > 0) + pwr += part_info(p).power; + return pwr; +} + +int vehicle::acceleration (bool fueled) +{ + return (int) (safe_velocity (fueled) * k_mass() / (1 + strain ()) / 10); +} + +int vehicle::max_velocity (bool fueled) +{ + return total_power (fueled) * 100; +} + +int vehicle::safe_velocity (bool fueled) +{ + int pwrs = 0; + int cnt = 0; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_engine) && + (fuel_left (part_info(p).fuel_type, true) || !fueled) && + parts[p].hp > 0) + { + int m2c = 100; + switch (part_info(p).fuel_type) + { + case AT_GAS: m2c = 60; break; + case AT_PLASMA: m2c = 75; break; + case AT_BATT: m2c = 90; break; + } + pwrs += part_info(p).power * m2c / 100; + cnt++; + } + if (cnt > 0) + pwrs = pwrs * 4 / (4 + cnt -1); + return (int) (pwrs * k_dynamics() * k_mass()) * 100; +} + +int vehicle::noise (bool fueled, bool gas_only) +{ + int pwrs = 0; + int cnt = 0; + int muffle = 100; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_muffler) && parts[p].hp > 0 && part_info(p).bonus < muffle) + muffle = part_info(p).bonus; + + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_engine) && + (fuel_left (part_info(p).fuel_type, true) || !fueled) && + parts[p].hp > 0) + { + int nc = 10; + switch (part_info(p).fuel_type) + { + case AT_GAS: nc = 25; break; + case AT_PLASMA: nc = 10; break; + case AT_BATT: nc = 3; break; + } + if (!gas_only || part_info(p).fuel_type == AT_GAS) + { + int pwr = part_info(p).power * nc / 100; + if (muffle < 100 && (part_info(p).fuel_type == AT_GAS || + part_info(p).fuel_type == AT_PLASMA)) + pwr = pwr * muffle / 100; + pwrs += pwr; + cnt++; + } + } + return pwrs; +} + +int vehicle::wheels_area (int *cnt) +{ + int count = 0; + int size = 0; + for (int i = 0; i < external_parts.size(); i++) + { + int p = external_parts[i]; + if (part_flag(p, vpf_wheel) && + parts[p].hp > 0) + { + size += part_info(p).size; + count++; + } + } + if (cnt) + *cnt = count; + return size; +} + +float vehicle::k_dynamics () +{ + int count; + const int max_obst = 13; + int obst[max_obst]; + for (int o = 0; o < max_obst; o++) + obst[o] = 0; + for (int i = 0; i < external_parts.size(); i++) + { + int p = external_parts[i]; + int frame_size = part_flag(p, vpf_obstacle)? 30 : 10; + int pos = parts[p].mount_dy + max_obst / 2; + if (pos < 0) + pos = 0; + if (pos >= max_obst) + pos = max_obst -1; + if (obst[pos] < frame_size) + obst[pos] = frame_size; + } + int frame_obst = 0; + for (int o = 0; o < max_obst; o++) + frame_obst += obst[o]; + float ae0 = 200.0; + float fr0 = 1000.0; + int wa = wheels_area(); + + // calculate aerodynamic coefficient + float ka = ae0 / (ae0 + frame_obst); + + // calculate safe speed reduction due to wheel friction + float kf = fr0 / (fr0 + wa); + + return ka * kf; +} + +float vehicle::k_mass () +{ + int wa = wheels_area(); + float ma0 = 50.0; + + // calculate safe speed reduction due to mass + float km = wa > 0? ma0 / (ma0 + total_mass() / 8 / (float) wa) : 0; + + return km; +} + +float vehicle::strain () +{ + int mv = max_velocity(); + int sv = safe_velocity(); + if (mv <= sv) + mv = sv + 1; + if (velocity < safe_velocity()) + return 0; + else + return (float) (velocity - sv) / (float) (mv - sv); +} + +bool vehicle::valid_wheel_config () +{ + int x1, y1, x2, y2; + int count = 0; + for (int i = 0; i < external_parts.size(); i++) + { + int p = external_parts[i]; + if (!part_flag(p, vpf_wheel) || + parts[p].hp <= 0) + continue; + if (!count) + { + x1 = x2 = parts[p].mount_dx; + y1 = y2 = parts[p].mount_dy; + } + if (parts[p].mount_dx < x1) + x1 = parts[p].mount_dx; + if (parts[p].mount_dx > x2) + x2 = parts[p].mount_dx; + if (parts[p].mount_dy < y1) + y1 = parts[p].mount_dy; + if (parts[p].mount_dy > y2) + y2 = parts[p].mount_dy; + count++; + } + if (count < 2) + return false; + float xo = 0, yo = 0; + float wo = 0, w2; + for (int p = 0; p < parts.size(); p++) + { // lets find vehicle's center of masses + w2 = g->itypes[part_info(p).item]->weight; + if (w2 < 1) + continue; + xo = xo * wo / (wo + w2) + parts[p].mount_dx * w2 / (wo + w2); + yo = yo * wo / (wo + w2) + parts[p].mount_dy * w2 / (wo + w2); + wo += w2; + } +// g->add_msg("cm x=%.3f y=%.3f m=%d x1=%d y1=%d x2=%d y2=%d", xo, yo, (int) wo, x1, y1, x2, y2); + if ((int)xo < x1 || (int)xo > x2 || (int)yo < y1 || (int)yo > y2) + return false; // center of masses not inside support of wheels (roughly) + return true; +} + +void vehicle::consume_fuel () +{ + int ftypes[3] = { AT_GAS, AT_BATT, AT_PLASMA }; + for (int ft = 0; ft < 3; ft++) + { + float st = strain() * 10; + int amnt = (int) (basic_consumption (ftypes[ft]) * (1.0 + st * st) / 100); + if (!amnt) + continue; // no engines of that type +// g->add_msg("consume: %d of fuel%d (st:%.2f)", amnt, ft, st); + bool elec = ftypes[ft] == AT_BATT; + bool found = false; + for (int j = 0; j < (elec? 2 : 1); j++) + { + for (int p = 0; p < parts.size(); p++) + // if this is a fuel tank + // and its type is same we're looking for now + // and for electric engines: + // - if j is 0, then we're looking for plutonium (it's first) + // - otherwise we're looking for batteries (second) + if (part_flag(p, vpf_fuel_tank) && + (part_info(p).fuel_type == (elec? (j? AT_BATT : AT_PLUT) : ftypes[ft]))) + { + parts[p].amount -= amnt; + if (parts[p].amount < 0) + parts[p].amount = 0; + found = true; + break; + } + if (found) + break; + } + } +} + +void vehicle::thrust (int thd) +{ + if (velocity == 0) + { + turn_dir = face.dir(); + last_turn = 0; + move = face; + moves = 0; + last_turn = 0; + skidding = false; + } + + if (!thd) + return; + + bool pl_ctrl = player_in_control(&g->u); + + if (!valid_wheel_config() && velocity == 0) + { + if (pl_ctrl) + g->add_msg ("The %s don't have enough wheels to move!", name.c_str()); + return; + } + + int sgn = velocity < 0? -1 : 1; + bool thrusting = sgn == thd; + + if (thrusting) + { + if (total_power () < 1) + { + if (pl_ctrl) + { + if (total_power (false) < 1) + g->add_msg ("The %s don't have engine!", name.c_str()); + else + g->add_msg ("The %s's engine emits sneezing sound.", name.c_str()); + } + cruise_velocity = 0; + return; + } + + consume_fuel (); + + int strn = (int) (strain () * strain() * 100); + + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_engine) && + (fuel_left (part_info(p).fuel_type, true)) && parts[p].hp > 0 && + rng (1, 100) < strn) + { + int dmg = rng (strn * 2, strn * 4); + damage_direct (p, dmg, 0); + } + + // add sound and smoke + int smk = noise (true, true); + if (smk > 0) + { + int rdx, rdy; + coord_translate (exhaust_dx, exhaust_dy, rdx, rdy); + g->m.add_field(g, global_x() + rdx, global_y() + rdy, fd_smoke, (smk / 50) + 1); + } + g->sound(global_x(), global_y(), noise(), ""); + } + + if (skidding) + return; + + int accel = acceleration(); + int max_vel = max_velocity(); + int brake = 30 * k_mass(); + int brk = abs(velocity) * brake / 100; + if (brk < accel) + brk = accel; + if (brk < 10 * 100) + brk = 10 * 100; + int vel_inc = (thrusting? accel : brk) * thd; + if ((velocity > 0 && velocity + vel_inc < 0) || + (velocity < 0 && velocity + vel_inc > 0)) + stop (); + else + { + velocity += vel_inc; + if (velocity > max_vel) + velocity = max_vel; + else + if (velocity < -max_vel / 4) + velocity = -max_vel / 4; + } +} + +void vehicle::cruise_thrust (int amount) +{ + if (!amount) + return; + int max_vel = (safe_velocity() * 11 / 10000 + 1) * 1000; + cruise_velocity += amount; + cruise_velocity = cruise_velocity / abs(amount) * abs(amount); + if (cruise_velocity > max_vel) + cruise_velocity = max_vel; + else + if (-cruise_velocity > max_vel / 4) + cruise_velocity = -max_vel / 4; +} + +void vehicle::turn (int deg) +{ + if (deg == 0) + return; + if (velocity < 0) + deg = -deg; + last_turn = deg; + turn_dir += deg; + if (turn_dir < 0) + turn_dir += 360; + if (turn_dir >= 360) + turn_dir -= 360; +} + +void vehicle::stop () +{ + velocity = 0; + skidding = false; + move = face; + last_turn = 0; + moves = 0; +} + +int vehicle::part_collision (int vx, int vy, int part, int x, int y) +{ + bool pl_ctrl = player_in_control (&g->u); + int mondex = g->mon_at(x, y); + int npcind = g->npc_at(x, y); + bool u_here = x == g->u.posx && y == g->u.posy && !g->u.in_vehicle; + monster *z = mondex >= 0? &g->z[mondex] : 0; + player *ph = (npcind >= 0? &g->active_npc[npcind] : (u_here? &g->u : 0)); + vehicle &oveh = g->m.veh_at (x, y); + bool veh_collision = oveh.type != veh_null && (oveh.posx != posx || oveh.posy != posy); + bool body_collision = (g->u.posx == x && g->u.posy == y && !g->u.in_vehicle) || + mondex >= 0 || npcind >= 0; + int dummy; + bool can_see = g->u_see(x, y, dummy); + + // 0 - nothing, 1 - monster/player/npc, 2 - vehicle, + // 3 - thin_obstacle, 4 - bashable, 5 - destructible, 6 - other + int collision_type = 0; + std::string obs_name = g->m.tername(x, y).c_str(); + + int parm = part_with_feature (part, vpf_armor); + if (parm < 0) + parm = part; + int dmg_mod = part_info(parm).dmg_mod; + // let's calculate type of collision & mass of object we hit + int mass = total_mass() / 8; + int mass2; + if (veh_collision) + { // first, check if we collide with another vehicle (there shouldn't be impassable terrain below) + collision_type = 2; // vehicle + mass2 = oveh.total_mass() / 8; + body_collision = false; + obs_name = oveh.name.c_str(); + } + else + if (body_collision) + { // then, check any monster/NPC/player on the way + collision_type = 1; // body + if (z) + switch (z->type->size) + { + case MS_TINY: // Rodent + mass2 = 15; + break; + case MS_SMALL: // Half human + mass2 = 40; + break; + default: + case MS_MEDIUM: // Human + mass2 = 80; + break; + case MS_LARGE: // Cow + mass2 = 200; + break; + case MS_HUGE: // TAAAANK + mass2 = 800; + break; + } + else + mass2 = 80;// player or NPC + } + else // if all above fails, go for terrain which might obstruct moving + if (g->m.has_flag_ter_only (thin_obstacle, x, y)) + { + collision_type = 3; // some fence + mass2 = 20; + } + else + if (g->m.has_flag_ter_only(bashable, x, y)) + { + collision_type = 4; // bashable (door, window) + mass2 = 50; // special case: instead of calculating absorb based on mass of obstacle later, we let + // map::bash function deside, how much absorb is + } + else + if (g->m.is_destructable_ter_only(x, y)) + { + collision_type = 5; // destructible (wall) + mass2 = 200; + } + else + if (g->m.move_cost_ter_only(x, y) == 0 && !g->m.has_flag_ter_only(swimmable, x, y)) + { + collision_type = 6; // not destructible + mass2 = 1000; + } + if (!collision_type) // hit nothing + return 0; + + int degree = rng (70, 100); + int imp = abs(velocity) * mass / k_mvel / 100 ; + int imp2 = imp * mass2 / (mass + mass2) * degree / 100; + bool smashed = true; + std::string snd; + if (collision_type == 4 || collision_type == 2) // something bashable -- use map::bash to determine outcome + { + int absorb = -1; + g->m.bash(x, y, imp * dmg_mod / 100, snd, &absorb); + if (absorb != -1) + imp2 = absorb; + smashed = imp * dmg_mod / 100 > absorb; + } + else + if (collision_type >= 3) // some other terrain + { + smashed = imp * rng (80, 120) / 100 > mass2; + if (smashed) + switch (collision_type) // destroy obstacle + { + case 3: + g->m.ter (x, y) = t_dirt; + break; + case 5: + g->m.ter(x, y) = t_rubble; + snd = "crash!"; + break; + case 6: + smashed = false; + break; + default:; + } + g->sound (x, y, smashed? 80 : 50, ""); + } + if (!body_collision) + { + if (pl_ctrl) + { + if (snd.length() > 0) + g->add_msg ("Your %s's %s rams into %s with a %s", name.c_str(), part_info(part).name, obs_name.c_str(), snd.c_str()); + else + g->add_msg ("Your %s's %s rams into %s.", name.c_str(), part_info(part).name, obs_name.c_str()); + } + else + if (snd.length() > 0) + g->add_msg ("You hear a %s", snd.c_str()); + } + if (part_flag(part, vpf_sharp) && smashed) + imp2 /= 2; + int imp1 = imp - imp2; + int vel1 = imp1 * k_mvel * 100 / mass; + int vel2 = imp2 * k_mvel * 100 / mass2; + +// g->add_msg ("Col t=%s i=%d i1=%d i2=%d v=%d v1=%d v2=%d m1=%d m2=%d", +// obs_name.c_str(), imp, imp1, imp2, abs(velocity), vel1, vel2, mass, mass2); +// + if (collision_type == 1) + { + int dam = imp1 * dmg_mod / 100; +// g->add_msg("dam=%d imp=%d dm=%d", dam, imp, parts[part].dmg_mod); + if (z) + { + int z_armor = part_flag(part, vpf_sharp)? z->type->armor_cut : z->type->armor_bash; + if (z_armor < 0) + z_armor = 0; + if (z) + dam -= z_armor; + } + if (dam < 0) + dam = 0; + + if (part_flag(part, vpf_sharp)) + parts[part].blood += (20 + dam) * 5; + else + if (dam > rng (10, 30)) + parts[part].blood += (10 + dam / 2) * 5; + + int turns_stunned = rng (0, dam) > 10? rng (1, 2) + (dam > 40? rng (1, 2) : 0) : 0; + if (part_flag(part, vpf_sharp)) + turns_stunned = 0; + if (turns_stunned > 6) + turns_stunned = 6; + if (turns_stunned > 0 && z) + z->add_effect(ME_STUNNED, turns_stunned); + + std::string dname; + if (z) + dname = z->name().c_str(); + else + dname = ph->name; + if (pl_ctrl) + g->add_msg ("Your %s's %s rams into %s, inflicting %d damage%s!", + name.c_str(), part_info(part).name, dname.c_str(), dam, + turns_stunned > 0 && z? " and stunning it" : ""); + + int angle = (100 - degree) * 2 * (one_in(2)? 1 : -1); + if (z) + { + z->hurt(dam); + if (vel2 > rng (5, 30)) + g->fling_player_or_monster (0, z, move.dir() + angle, vel2 / 100); + if (z->hp < 1) + g->kill_mon (mondex); + } + else + { + ph->hitall (g, dam, 40); + if (vel2 > rng (5, 30)) + g->fling_player_or_monster (ph, 0, move.dir() + angle, vel2 / 100); + } + + if (part_flag(part, vpf_sharp)) + { + if (g->m.field_at(x, y).type == fd_blood && + g->m.field_at(x, y).density < 2) + g->m.field_at(x, y).density++; + else + g->m.add_field(g, x, y, fd_blood, 1); + } + else + g->sound (x, y, 20, ""); + } + + if (!smashed || collision_type == 2) // vehicles shouldn't intersect + { + cruise_on = false; + stop(); + imp2 = imp; + } + else + { + if (vel1 < 500) + stop(); + else + { + if (velocity < 0) + velocity = -vel1; + else + velocity = vel1; + } + + int turn_roll = rng (0, 100); + int turn_amount = rng (1, 3) * sqrt (imp2); + turn_amount /= 15; + if (turn_amount < 1) + turn_amount = 1; + turn_amount *= 15; + if (turn_amount > 120) + turn_amount = 120; + bool turn_veh = turn_roll < (abs(velocity) - vel1) / 100; + if (turn_veh) + { + skidding = true; + turn (one_in (2)? turn_amount : -turn_amount); + } + + } + damage (parm, imp2, 1); + return imp2; +} + +void vehicle::handle_trap (int x, int y, int part) +{ + int pwh = part_with_feature (part, vpf_wheel); + if (pwh < 0) + return; + trap_id t = g->m.tr_at(x, y); + if (t == tr_null) + return; + int noise = 0; + int chance = 100; + int expl = 0; + int shrap = 0; + bool wreckit = false; + std::string msg ("The %s's %s runs over %s."); + std::string snd; + switch (t) + { + case tr_bubblewrap: + noise = 18; + snd = "Pop!"; + break; + case tr_beartrap: + case tr_beartrap_buried: + noise = 8; + snd = "SNAP!"; + wreckit = true; + g->m.tr_at(x, y) = tr_null; + g->m.add_item(x, y, g->itypes[itm_beartrap], 0); + break; + case tr_nailboard: + wreckit = true; + break; + case tr_blade: + noise = 1; + snd = "Swinnng!"; + wreckit = true; + break; + case tr_crossbow: + chance = 30; + noise = 1; + snd = "Clank!"; + wreckit = true; + g->m.tr_at(x, y) = tr_null; + g->m.add_item(x, y, g->itypes[itm_crossbow], 0); + g->m.add_item(x, y, g->itypes[itm_string_6], 0); + if (!one_in(10)) + g->m.add_item(x, y, g->itypes[itm_bolt_steel], 0); + break; + case tr_shotgun_2: + case tr_shotgun_1: + noise = 60; + snd = "Bang!"; + chance = 70; + wreckit = true; + if (t == tr_shotgun_2) + g->m.tr_at(x, y) = tr_shotgun_1; + else + { + g->m.tr_at(x, y) = tr_null; + g->m.add_item(x, y, g->itypes[itm_shotgun_sawn], 0); + g->m.add_item(x, y, g->itypes[itm_string_6], 0); + } + break; + case tr_landmine: + expl = 10; + shrap = 8; + break; + case tr_boobytrap: + expl = 18; + shrap = 12; + break; + case tr_dissector: + noise = 10; + snd = "BRZZZAP!"; + wreckit = true; + break; + case tr_sinkhole: + case tr_pit: + case tr_spike_pit: + case tr_ledge: + wreckit = true; + break; + case tr_goo: + case tr_portal: + case tr_telepad: + case tr_temple_flood: + case tr_temple_toggle: + msg.clear(); + default:; + } + int dummy; + if (msg.size() > 0 && g->u_see(x, y, dummy)) + g->add_msg (msg.c_str(), name.c_str(), part_info(part).name, g->traps[t]->name.c_str()); + if (noise > 0) + g->sound (x, y, noise, snd); + if (wreckit && chance >= rng (1, 100)) + damage (part, 500); + if (expl > 0) + g->explosion(x, y, expl, shrap, false); +} + +bool vehicle::add_item (int part, item itm) +{ + if (!part_flag(part, vpf_cargo) || parts[part].items.size() >= 26) + return false; + it_ammo *ammo = dynamic_cast (itm.type); + if (part_flag(part, vpf_turret)) + if (!ammo || (ammo->type != part_info(part).fuel_type || + ammo->type == AT_GAS || + ammo->type == AT_PLASMA)) + return false; + parts[part].items.push_back (itm); + return true; +} + +void vehicle::remove_item (int part, int itemdex) +{ + if (itemdex < 0 || itemdex >= parts[part].items.size()) + return; + parts[part].items.erase (parts[part].items.begin() + itemdex); +} + +void vehicle::gain_moves (int mp) +{ + moves += mp; + // cruise control TODO: enable for NPC? + if (player_in_control(&g->u)) + { + if (cruise_on) + if (abs(cruise_velocity - velocity) >= acceleration()/2 || + (cruise_velocity != 0 && velocity == 0) || + (cruise_velocity == 0 && velocity != 0)) + thrust (cruise_velocity > velocity? 1 : -1); + } + + if (g->is_in_sunlight(global_x(), global_y())) + { + int spw = solar_power (); + if (spw) + { + int fl = spw / 100; + int prob = spw % 100; + if (rng (0, 100) <= prob) + fl++; + if (fl) + refill (AT_BATT, fl); + } + } + // check for smoking parts + for (int ep = 0; ep < external_parts.size(); ep++) + { + int p = external_parts[ep]; + if (parts[p].blood > 0) + parts[p].blood--; + int p_eng = part_with_feature (p, vpf_engine, false); + if (p_eng < 0 || parts[p_eng].hp > 0 || parts[p_eng].amount < 1) + continue; + parts[p_eng].amount--; + int x = global_x() + parts[p_eng].precalc_dx[0]; + int y = global_y() + parts[p_eng].precalc_dy[0]; + for (int ix = -1; ix <= 1; ix++) + for (int iy = -1; iy <= 1; iy++) + if (!rng(0, 2)) + g->m.add_field(g, x + ix, y + iy, fd_smoke, rng(2, 4)); + } + + if (turret_mode) // handle turrets + for (int p = 0; p < parts.size(); p++) + fire_turret (p); +} + +void vehicle::find_external_parts () +{ + external_parts.clear(); + for (int p = 0; p < parts.size(); p++) + { + bool ex = false; + for (int i = 0; i < external_parts.size(); i++) + if (parts[external_parts[i]].mount_dx == parts[p].mount_dx && + parts[external_parts[i]].mount_dy == parts[p].mount_dy) + { + ex = true; + break; + } + if (!ex) + external_parts.push_back (p); + } +} + +void vehicle::find_exhaust () +{ + int en = -1; + for (int p = 0; p < parts.size(); p++) + if (part_flag(p, vpf_engine) && part_info(p).fuel_type == AT_GAS) + { + en = p; + break; + } + if (en < 0) + { + exhaust_dy = 0; + exhaust_dx = 0; + return; + } + exhaust_dy = parts[en].mount_dy; + exhaust_dx = parts[en].mount_dx; + for (int p = 0; p < parts.size(); p++) + if (parts[p].mount_dy == exhaust_dy && + parts[p].mount_dx < exhaust_dx) + exhaust_dx = parts[p].mount_dx; + exhaust_dx--; +} + +void vehicle::refresh_insides () +{ + insides_dirty = false; + for (int ep = 0; ep < external_parts.size(); ep++) + { + int p = external_parts[ep]; + if (part_with_feature(p, vpf_roof) < 0 || parts[p].hp <= 0) + { // if there's no roof (or it's broken) -- it's outside! +/* debugmsg ("part%d/%d(%s)%d,%d no roof=false", p, external_parts.size(), + part_info(p).name, parts[p].mount_dx, parts[p].mount_dy);*/ + parts[p].inside = false; + continue; + } + + parts[p].inside = true; // inside if not otherwise + for (int i = 0; i < 4; i++) + { // let's check four neighbour parts + int ndx = i < 2? (i == 0? -1 : 1) : 0; + int ndy = i < 2? 0 : (i == 2? - 1: 1); + std::vector parts_n3ar = parts_at_relative (parts[p].mount_dx + ndx, parts[p].mount_dy + ndy); + bool cover = false; // if we aren't covered from sides, the roof at p won't save us + for (int j = 0; j < parts_n3ar.size(); j++) + { + int pn = parts_n3ar[j]; + if (parts[pn].hp <= 0) + continue; // it's broken = can't cover + if (part_flag(pn, vpf_roof)) + { // another roof -- cover + cover = true; + break; + } + else + if (part_flag(pn, vpf_obstacle)) + { // found an obstacle, like board or windshield or door + if (part_flag(pn, vpf_openable) && parts[pn].open) + continue; // door and it's open -- can't cover + cover = true; + break; + } + } + if (!cover) + { +/* debugmsg ("part%d/%d(%s)%d,%d nb#%d(%s) no cover=false", p, external_parts.size(), + part_info(p).name, parts[p].mount_dx, parts[p].mount_dy, + i, parts_n3ar.size()> 0? part_info(parts_n3ar[0]).name : "");*/ + parts[p].inside = false; + break; + } + } + } +} + +bool vehicle::is_inside (int p) +{ + if (p < 0 || p >= parts.size()) + return false; + if (insides_dirty) + refresh_insides (); + return parts[p].inside; +} + +void vehicle::unboard_all () +{ + std::vector bp = boarded_parts (); + for (int i = 0; i < bp.size(); i++) + g->m.unboard_vehicle (g, global_x() + parts[bp[i]].precalc_dx[0], global_y() + parts[bp[i]].precalc_dy[0]); +} + +int vehicle::damage (int p, int dmg, int type, bool aimed) +{ + if (dmg < 1) + return dmg; + + std::vector pl = internal_parts (p); + pl.insert (pl.begin(), p); + if (!aimed) + { + bool found_obs = false; + for (int i = 0; i < pl.size(); i++) + if (part_flag (pl[i], vpf_obstacle) && + (!part_flag (pl[i], vpf_openable) || !parts[pl[i]].open)) + { + found_obs = true; + break; + } + if (!found_obs) // not aimed at this tile and no obstacle here -- fly through + return dmg; + } + int parm = part_with_feature (p, vpf_armor); + int pdm = pl[rng (0, pl.size()-1)]; + int dres; + if (parm < 0) + // not covered by armor -- damage part + dres = damage_direct (pdm, dmg, type); + else + { + // covered by armor -- damage armor first + dres = damage_direct (parm, dmg, type); + // half damage for internal part(over parts not covered) + damage_direct (pdm, part_flag(pdm, vpf_over)? dmg : dmg / 2, type); + } + return dres; +} + +void vehicle::damage_all (int dmg1, int dmg2, int type) +{ + if (dmg2 < dmg1) + { + int t = dmg2; + dmg2 = dmg1; + dmg1 = t; + } + if (dmg1 < 1) + return; + for (int p = 0; p < parts.size(); p++) + if (!one_in(4)) + damage_direct (p, rng (dmg1, dmg2), type); +} + +int vehicle::damage_direct (int p, int dmg, int type) +{ + if (parts[p].hp <= 0) + return dmg; + int tsh = part_info(p).durability / 10; + if (tsh > 20) + tsh = 20; + int dres = dmg; + if (dmg >= tsh || type != 1) + { + dres -= parts[p].hp; + int last_hp = parts[p].hp; + parts[p].hp -= dmg; + if (parts[p].hp < 0) + parts[p].hp = 0; + if (!parts[p].hp && last_hp > 0) + insides_dirty = true; + if (part_flag(p, vpf_fuel_tank)) + { + int ft = part_info(p).fuel_type; + if (ft == AT_GAS || ft == AT_PLASMA) + { + int pow = parts[p].amount / 40; + // debugmsg ("damage check dmg=%d pow=%d", dmg, pow); + if (parts[p].hp <= 0) + leak_fuel (p); + if (type == 2 || + (one_in (ft == AT_GAS? 2 : 4) && pow > 5 && rng (75, 150) < dmg)) + { + g->explosion (global_x() + parts[p].precalc_dx[0], global_y() + parts[p].precalc_dy[0], + pow, 0, ft == AT_GAS); + parts[p].hp = 0; + } + } + } + else + if (parts[p].hp <= 0 && part_flag(p, vpf_unmount_on_damage)) + { + g->m.add_item (global_x() + parts[p].precalc_dx[0], + global_y() + parts[p].precalc_dy[0], + g->itypes[part_info(p).item], g->turn); + remove_part (p); + } + } + if (dres < 0) + dres = 0; + return dres; +} + +void vehicle::leak_fuel (int p) +{ + if (!part_flag(p, vpf_fuel_tank)) + return; + int ft = part_info(p).fuel_type; + if (ft == AT_GAS) + { + int x = global_x(); + int y = global_y(); + for (int i = x - 2; i <= x + 2; i++) + for (int j = y - 2; j <= y + 2; j++) + if (g->m.move_cost(i, j) > 0 && one_in(2)) + { + if (parts[p].amount < 100) + { + parts[p].amount = 0; + return; + } + g->m.add_item(i, j, g->itypes[itm_gasoline], 0); + parts[p].amount -= 100; + } + } + parts[p].amount = 0; +} + +void vehicle::fire_turret (int p, bool burst) +{ + if (!part_flag (p, vpf_turret)) + return; + it_gun *gun = dynamic_cast (g->itypes[part_info(p).item]); + if (!gun) + return; + int charges = burst? gun->burst : 1; + if (!charges) + charges = 1; + int amt = part_info (p).fuel_type; + if (amt == AT_GAS || amt == AT_PLASMA) + { + if (amt == AT_GAS) + charges = 20; // hacky + int fleft = fuel_left (amt); + if (fleft < 1) + return; + it_ammo *ammo = dynamic_cast(g->itypes[amt == AT_GAS? itm_gasoline : itm_plasma]); + if (!ammo) + return; + if (fire_turret_internal (p, *gun, *ammo, charges)) + { // consume fuel + if (amt == AT_PLASMA) + charges *= 10; // hacky, too + for (int p = 0; p < parts.size(); p++) + { + if (part_flag(p, vpf_fuel_tank) && + part_info(p).fuel_type == amt && + parts[p].amount > 0) + { + parts[p].amount -= charges; + if (parts[p].amount < 0) + parts[p].amount = 0; + } + } + } + } + else + { + if (parts[p].items.size() > 0) + { + it_ammo *ammo = dynamic_cast (parts[p].items[0].type); + if (!ammo || ammo->type != amt || + parts[p].items[0].charges < 1) + return; + if (charges > parts[p].items[0].charges) + charges = parts[p].items[0].charges; + if (fire_turret_internal (p, *gun, *ammo, charges)) + { // consume ammo + if (charges >= parts[p].items[0].charges) + parts[p].items.erase (parts[p].items.begin()); + else + parts[p].items[0].charges -= charges; + } + } + } +} + +bool vehicle::fire_turret_internal (int p, it_gun &gun, it_ammo &ammo, int charges) +{ + int x = global_x() + parts[p].precalc_dx[0]; + int y = global_y() + parts[p].precalc_dy[0]; + // code copied form mattack::smg, mattack::flamethrower + int t, j, fire_t; + monster *target = 0; + int range = ammo.type == AT_GAS? 5 : 12; + int closest = range + 1; + for (int i = 0; i < g->z.size(); i++) + { + int dist = rl_dist(x, y, g->z[i].posx, g->z[i].posy); + if (g->z[i].friendly == 0 && dist < closest && + g->m.sees(x, y, g->z[i].posx, g->z[i].posy, range, t)) + { + target = &(g->z[i]); + closest = dist; + fire_t = t; + } + } + if (!target) + return false; + + std::vector traj = line_to(x, y, target->posx, target->posy, fire_t); + for (int i = 0; i < traj.size(); i++) + if (traj[i].x == g->u.posx && traj[i].y == g->u.posy) + return false; // won't shoot at player + if (g->u_see(x, y, t)) + g->add_msg("The %s fires its %s!", name.c_str(), part_info(p).name); + player tmp; + tmp.name = std::string("The ") + part_info(p).name; + tmp.sklevel[gun.skill_used] = 1; + tmp.sklevel[sk_gun] = 0; + tmp.recoil = abs(velocity) / 100 / 4; + tmp.posx = x; + tmp.posy = y; + tmp.str_cur = 16; + tmp.dex_cur = 6; + tmp.per_cur = 8; + tmp.weapon = item(&gun, 0); + it_ammo curam = ammo; + tmp.weapon.curammo = &curam; + tmp.weapon.charges = charges; + g->fire(tmp, target->posx, target->posy, traj, true); + if (ammo.type == AT_GAS) + { + for (int i = 0; i < traj.size(); i++) + g->m.add_field(g, traj[i].x, traj[i].y, fd_fire, 1); + } +} + diff --git a/vehicle.h b/vehicle.h new file mode 100644 index 0000000000..34a4972eb6 --- /dev/null +++ b/vehicle.h @@ -0,0 +1,334 @@ +#ifndef _VEHICLE_H_ +#define _VEHICLE_H_ + +#include "tileray.h" +#include "color.h" +#include "item.h" +#include "veh_type.h" +#include +#include +#include + +class map; +class player; +class game; + +const int num_fuel_types = 4; +const int fuel_types[num_fuel_types] = { AT_GAS, AT_BATT, AT_PLUT, AT_PLASMA }; +const int k_mvel = 200; + +// Structure, describing vehicle part (ie, wheel, seat) +struct vehicle_part +{ + vpart_id id; // id in list of parts (vpart_list index) + int mount_dx; // mount point on the forward/backward axis + int mount_dy; // mount point on the left/right axis + int precalc_dx[2]; // mount_dx translated to face.dir [0] and turn_dir [1] + int precalc_dy[2]; // mount_dy translated to face.dir [0] and turn_dir [1] + int hp; // current durability, if 0, then broken + int blood; // how much blood covers part (in turns). only useful for external + bool inside; // if tile provides cover. WARNING: do not read it directly, use vehicle::is_inside() instead + union + { + int amount; // amount of fuel for tank + int open; // door is open + int passenger; // seat has passenger + }; + std::vector items;// inventory +}; + +// Facts you need to know about implementation: +// - Vehicles belong to map. There's std::vector +// for each submap in grid. When requesting a reference +// to vehicle, keep in mind it can be invalidated +// by functions such as map::displace_vehicle. +// - To check if there's any vehicle at given map tile, +// call map::veh_at, and check vehicle type (veh_null +// means there's no vehicle there). +// - Vehicle consists of parts (represented by vector). Parts +// have some constant info: see veh_type.h, vpart_info structure +// and vpart_list array -- that is accessible through part_info method. +// The second part is variable info, see vehicle_part structure +// just above. +// - Parts are mounted at some point relative to vehicle position (or starting part) +// (0, 0 in mount coords). There can be more than one part at +// given mount coords. First one is considered external, +// others are internal (or, as special case, "over" -- like trunk) +// Check tileray.h file to see a picture of coordinate axes. +// - Vehicle can be rotated to arbitrary degree. This means that +// mount coords are rotated to match vehicle's face direction before +// their actual positions are known. For optimization purposes +// mount coords are precalculated for current vehicle face direction +// and stored in precalc_*[0]. precalc_*[1] stores mount coords for +// next move (vehicle can move and turn). Method map::displace vehicle +// assigns precalc[1] to precalc[0]. At any time (except +// map::vehmove innermost cycle) you can get actual part coords +// relative to vehicle's position by reading precalc_*[0]. +// - Vehicle keeps track of 3 directions: +// face (where it's facing currently) +// move (where it's moving, it's different from face if it's skidding) +// turn_dir (where it will turn at next move, if it won't stop due to collision) +// - Some methods take "part" or "p" parameter. Some of them +// assume that's external part number, and all internal parts +// at this mount point are affected. There is separate +// vector in which a list of external part is stored, +// it must correspond to actual list of external parts +// (assure this if you add/remove parts programmatically). +// - Driver doesn't know what vehicle he drives. +// There's only player::in_vehicle flag which +// indicates that he is inside vehicle. To figure +// out what, you need to ask a map if there's a vehicle +// at driver/passenger position. +// - To keep info consistent, always use +// map::board_vehicle and map::unboard_vehicle for +// boarding/unboarding player. +// - To add new predesigned vehicle, assign new value for vhtype_id enum +// and declare vehicle and add parts in file veh_typedef.cpp, using macroes, +// similar to existant. Keep in mind, that positive x coordinate points +// forwards, negative x is back, positive y is to the right, and +// negative y to the left: +// orthogonal dir left (-Y) +// ^ +// -X -------> +X (forward) +// v +// orthogonal dir right (+Y) +// When adding parts, function checks possibility to install part at given +// coords. If it shows debug messages that it can't add parts, when you start +// the game, you did something wrong. +// There are a few rules: some parts are external, so one should be the first part +// at given mount point (tile). They require some part in neighbouring tile (with vpf_mount_point flag) to +// be mounted to. Other parts are internal or placed over. They can only be installed on top +// of external part. Some functional parts can be only in single instance per tile, i. e., +// no two engines at one mount point. +// If you can't understand, why installation fails, try to assemble your vehicle in game first. +class vehicle +{ +private: + game *g; + +public: + vehicle (game *ag=0, vhtype_id type_id = veh_null); + ~vehicle (); + +// check if given player controls this vehicle + bool player_in_control (player *p); + +// init parts state for randomly generated vehicle + void init_state(); + +// load and init vehicle data from stream. This implies valid save data! + void load (std::ifstream &stin); + +// Save vehicle data to stream + void save (std::ofstream &stout); + +// get vpart type info for part number (part at given vector index) + const vpart_info& part_info (int index); + +// check if certain part can be mounted at certain position (not accounting frame direction) + bool can_mount (int dx, int dy, vpart_id id); + +// check if certain external part can be unmounted + bool can_unmount (int p); + +// install a new part to vehicle (force to skip possibility check) + int install_part (int dx, int dy, vpart_id id, int hp = -1, bool force = false); + + void remove_part (int p); + +// returns the list of indeces of parts at certain position (not accounting frame direction) + std::vector parts_at_relative (int dx, int dy); + +// returns the list of indeces of parts inside (or over) given + std::vector internal_parts (int p); + +// returns index of part, inner to given, with certain flag (WARNING: without mfb!), or -1 + int part_with_feature (int p, unsigned int f, bool unbroken = true); + +// returns true if given flag is present for given part index (WARNING: without mfb!) + bool part_flag (int p, unsigned int f); + +// Translate seat-relative mount coords into tile coords + void coord_translate (int reldx, int reldy, int &dx, int &dy); + +// Translate seat-relative mount coords into tile coords using given face direction + void coord_translate (int dir, int reldx, int reldy, int &dx, int &dy); + +// Seek a vehicle part which obstructs tile with given coords relative to vehicle position + int part_at (int dx, int dy); + +// get symbol for map + char part_sym (int p); + +// get color for map + nc_color part_color (int p); + +// Vehicle parts description + void print_part_desc (void *w, int y1, int width, int p, int hl = -1); + +// Vehicle fuel indicator + void print_fuel_indicator (void *w, int y, int x); + +// Precalculate mount points for (idir=0) - current direction or (idir=1) - next turn direction + void precalc_mounts (int idir, int dir); + +// get a list of part indeces where is a passenger inside + std::vector boarded_parts(); + +// get passenger at part p + player *get_passenger (int p); + +// get global coords for vehicle + int global_x (); + int global_y (); + +// Checks how much certain fuel left in tanks. If for_engine == true that means +// ftype == AT_BATT is also takes in account AT_PLUT fuel (electric motors can use both) + int fuel_left (int ftype, bool for_engine = false); + int fuel_capacity (int ftype); + + // refill fuel tank(s) with given type of fuel + // returns amount of leftover fuel + int refill (int ftype, int amount); + +// vehicle's fuel type name + std::string fuel_name(int ftype); + +// fuel consumption of vehicle engines of given type, in one-hundreth of fuel + int basic_consumption (int ftype); + + void consume_fuel (); + +// get the total mass of vehicle, including cargo and passengers + int total_mass (); + +// Get combined power of all engines. If fueled == true, then only engines which +// vehicle have fuel for are accounted + int total_power (bool fueled = true); + +// Get combined power of solar panels + int solar_power (); + +// Get acceleration gained by combined power of all engines. If fueled == true, then only engines which +// vehicle have fuel for are accounted + int acceleration (bool fueled = true); + +// Get maximum velocity gained by combined power of all engines. If fueled == true, then only engines which +// vehicle have fuel for are accounted + int max_velocity (bool fueled = true); + +// Get safe velocity gained by combined power of all engines. If fueled == true, then only engines which +// vehicle have fuel for are accounted + int safe_velocity (bool fueled = true); + + int noise (bool fueled = true, bool gas_only = false); + +// Calculate area covered by wheels and, optionally count number of wheels + int wheels_area (int *cnt = 0); + +// Combined coefficient of aerodynamic and wheel friction resistance of vehicle, 0-1.0. +// 1.0 means it's ideal form and have no resistance at all. 0 -- it won't move + float k_dynamics (); + +// Coefficient of mass, 0-1.0. +// 1.0 means mass won't slow vehicle at all, 0 - it won't move + float k_mass (); + +// strain of engine(s) if it works higher that safe speed (0-1.0) + float strain (); + +// calculate if it can move using its wheels configuration + bool valid_wheel_config (); + +// thrust (1) or brake (-1) vehicle + void thrust (int thd); + +// cruise control + void cruise_thrust (int amount); + +// turn vehicle left (negative) or right (positive), degrees + void turn (int deg); + +// handle given part collision with vehicle, monster/NPC/player or terrain obstacle +// return impulse (damage) applied on vehicle for that collision + int part_collision (int vx, int vy, int part, int x, int y); + +// Process the trap beneath + void handle_trap (int x, int y, int part); + +// add item to part's cargo. if false, then there's no cargo at this part or cargo is full + bool add_item (int part, item itm); + +// remove item from part's cargo + void remove_item (int part, int itemdex); + + void gain_moves (int mp); + +// reduces velocity to 0 + void stop (); + + void find_external_parts (); + + void find_exhaust (); + + void refresh_insides (); + + bool is_inside (int p); + + void unboard_all (); + + // damage types: + // 0 - piercing + // 1 - bashing (damage applied if it passes certain treshold) + // 2 - incendiary + // damage individual external part. bash means damage + // must exceed certain threshold to be substracted from hp + // (a lot light collisions will not destroy parts) + // returns damage bypassed + int damage (int p, int dmg, int type = 1, bool aimed = true); + + // damage all parts (like shake from strong collision), range from dmg1 to dmg2 + void damage_all (int dmg1, int dmg2, int type = 1); + + // direct damage to part (armor protection and internals are not counted) + // returns damage bypassed + int damage_direct (int p, int dmg, int type = 1); + + void leak_fuel (int p); + + // fire the turret which is part p + void fire_turret (int p, bool burst = true); + + // internal procedure of turret firing + bool fire_turret_internal (int p, it_gun &gun, it_ammo &ammo, int charges); + + // upgrades/refilling/etc. see veh_interact.cpp + void interact (); + + // config values + std::string name; // vehicle name + int type; // vehicle type + std::vector parts; // Parts which occupy different tiles + std::vector external_parts; // List of external parts indeces + int exhaust_dx; + int exhaust_dy; + + // temp values + int smx, smy; // submap coords. WARNING: must ALWAYS correspond to sumbap coords in grid, or i'm out + bool insides_dirty; // if true, then parts' "inside" flags are outdated and need refreshing + + // save values + int posx, posy; + tileray face; // frame direction + tileray move; // direction we are moving + int velocity; // vehicle current velocity, mph * 100 + int cruise_velocity; // velocity vehicle's cruise control trying to acheive + bool cruise_on; // cruise control on/off + int turn_dir; // direction, to wich vehicle is turning (player control). will rotate frame on next move + bool skidding; // skidding mode + int last_turn; // amount of last turning (for calculate skidding due to handbrake) + int moves; + int turret_mode; // turret firing mode: 0 = off, 1 = burst fire +}; + +#endif From 20fb52e49ca5733b72aed702f7713a7c1d09fc8e Mon Sep 17 00:00:00 2001 From: Whales Date: Sat, 30 Jun 2012 22:29:30 -0400 Subject: [PATCH 32/51] Fixed some NPC aggression issues. --- npc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npc.cpp b/npc.cpp index 076cef1dcd..6b8b186250 100644 --- a/npc.cpp +++ b/npc.cpp @@ -1362,7 +1362,7 @@ void npc::form_opinion(player *u) if (op_of_u.fear < personality.bravery + 3 && op_of_u.fear - personality.aggression > -8 && op_of_u.trust > -4) attitude = NPCATT_TALK; - else if (op_of_u.fear - 2 * personality.aggression - personality.bravery < -8) + else if (op_of_u.fear - 2 * personality.aggression - personality.bravery < -30) attitude = NPCATT_KILL; else attitude = NPCATT_FLEE; @@ -1438,7 +1438,7 @@ bool npc::turned_hostile() int npc::hostile_anger_level() { - return (10 + op_of_u.fear - personality.aggression); + return (20 + op_of_u.fear - personality.aggression); } void npc::make_angry() From 95974473e6f222c6f188850b4154d2f2a7140c33 Mon Sep 17 00:00:00 2001 From: Whales Date: Sat, 30 Jun 2012 22:37:52 -0400 Subject: [PATCH 33/51] Fixed a crash when asking NPC to lead you to safety. --- npctalk.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/npctalk.cpp b/npctalk.cpp index 511cf6b9d9..a014a489b1 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -1380,7 +1380,8 @@ void talk_function::player_weapon_drop(game *g, npc *p) void talk_function::lead_to_safety(game *g, npc *p) { g->give_mission(MISSION_REACH_SAFETY); - point target = g->find_mission( g->u.active_mission )->target; + int missid = g->u.active_missions[g->u.active_mission]; + point target = g->find_mission( missid )->target; p->goalx = target.x; p->goaly = target.y; p->attitude = NPCATT_LEAD; From 5f39a42f32a7a1c76e5948e1bea80f1e23d5c5ad Mon Sep 17 00:00:00 2001 From: Whales Date: Mon, 2 Jul 2012 01:47:46 -0400 Subject: [PATCH 34/51] Several game-breaking bug fixes. Good night. --- item.cpp | 1 + map.cpp | 2 +- monitemsdef.cpp | 2 ++ npc.cpp | 8 +++++--- npctalk.cpp | 10 +++++----- player.cpp | 2 +- vehicle.cpp | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/item.cpp b/item.cpp index fa12c5d95c..cfbf40121e 100644 --- a/item.cpp +++ b/item.cpp @@ -1309,6 +1309,7 @@ std::string default_technique_name(technique_id tech) case TEC_WBLOCK_3: return "Shield"; case TEC_COUNTER: return "Counter-attack"; case TEC_BREAK: return "Grab break"; + case TEC_DISARM: return "Disarm"; case TEC_DEF_THROW: return "Defensive throw"; case TEC_DEF_DISARM: return "Defense disarm"; default: return "A BUG!"; diff --git a/map.cpp b/map.cpp index 0a68a423ed..18512d013f 100644 --- a/map.cpp +++ b/map.cpp @@ -716,7 +716,7 @@ bool map::is_destructable(int x, int y) bool map::is_destructable_ter_only(int x, int y) { return (has_flag_ter_only(bashable, x, y) || - (move_cost(x, y) == 0 && !has_flag(liquid, x, y))); + (move_cost_ter_only(x, y) == 0 && !has_flag(liquid, x, y))); } bool map::is_outside(int x, int y) diff --git a/monitemsdef.cpp b/monitemsdef.cpp index c20685c18e..0805a2e0bd 100644 --- a/monitemsdef.cpp +++ b/monitemsdef.cpp @@ -23,6 +23,8 @@ void game::init_monitems() monitems[mon_boomer] = monitems[mon_zombie]; monitems[mon_boomer_fungus] = monitems[mon_zombie]; monitems[mon_zombie_necro] = monitems[mon_zombie]; + monitems[mon_zombie_grabber] = monitems[mon_zombie]; + monitems[mon_zombie_master] = monitems[mon_zombie]; setvector(monitems[mon_zombie_scientist], mi_dresser, 10,mi_harddrugs, 6,mi_chemistry, 10, diff --git a/npc.cpp b/npc.cpp index 6b8b186250..95a9b542fb 100644 --- a/npc.cpp +++ b/npc.cpp @@ -48,6 +48,8 @@ npc::npc() mission = NPC_MISSION_NULL; myclass = NC_NONE; patience = 0; + for (int i = 0; i < num_skill_types; i++) + sklevel[i] = 0; } npc::npc(const npc &rhs) @@ -1330,7 +1332,7 @@ void npc::form_opinion(player *u) if (u->weapon.is_gun()) op_of_u.trust -= 2; - else if (u->weapon.type->id == 0) + else if (u->unarmed_attack()) op_of_u.trust += 2; if (u->has_disease(DI_HIGH)) @@ -1359,8 +1361,8 @@ void npc::form_opinion(player *u) op_of_u.value += 2; } - if (op_of_u.fear < personality.bravery + 3 && - op_of_u.fear - personality.aggression > -8 && op_of_u.trust > -4) + if (op_of_u.fear < 2 * personality.bravery + 3 && + op_of_u.fear - personality.aggression > -10 && op_of_u.trust > -8) attitude = NPCATT_TALK; else if (op_of_u.fear - 2 * personality.aggression - personality.bravery < -30) attitude = NPCATT_KILL; diff --git a/npctalk.cpp b/npctalk.cpp index a014a489b1..f85fc5ec74 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -57,10 +57,10 @@ void npc::talk_to_u(game *g) if (attitude == NPCATT_TALK) attitude = NPCATT_NULL; else if (attitude == NPCATT_FLEE) { - g->add_msg("%d is fleeing you!", name.c_str()); + g->add_msg("%s is fleeing you!", name.c_str()); return; } else if (attitude == NPCATT_KILL) { - g->add_msg("%d is hostile!", name.c_str()); + g->add_msg("%s is hostile!", name.c_str()); return; } dialogue d; @@ -1438,10 +1438,10 @@ void talk_function::start_training(game *g, npc *p) } // Pay for it - if (p->op_of_u.owed >= cost) - p->op_of_u.owed -= cost; + if (p->op_of_u.owed >= 0 - cost) + p->op_of_u.owed += cost; else if (!trade(g, p, cost, "Pay for training:")) - return; + return; // Then receive it g->u.assign_activity(ACT_TRAIN, time, p->chatbin.tempvalue); } diff --git a/player.cpp b/player.cpp index c4fc4c2f0b..c8b1d9f2a7 100644 --- a/player.cpp +++ b/player.cpp @@ -549,7 +549,7 @@ std::string player::save_info() dump << styles.size() << " "; for (int i = 0; i < styles.size(); i++) - dump << int(styles[i]); + dump << int(styles[i]) << " "; dump << illness.size() << " "; for (int i = 0; i < illness.size(); i++) diff --git a/vehicle.cpp b/vehicle.cpp index 4f790a4685..474a6f0e75 100644 --- a/vehicle.cpp +++ b/vehicle.cpp @@ -1083,7 +1083,7 @@ int vehicle::part_collision (int vx, int vy, int part, int x, int y) // map::bash function deside, how much absorb is } else - if (g->m.is_destructable_ter_only(x, y)) + if (g->m.move_cost_ter_only(x, y) == 0 && g->m.is_destructable_ter_only(x, y)) { collision_type = 5; // destructible (wall) mass2 = 200; From 7af5bcea66a6eeab93486b63f8a6dfb52ec4e040 Mon Sep 17 00:00:00 2001 From: Whales Date: Tue, 3 Jul 2012 01:26:56 -0400 Subject: [PATCH 35/51] More fixes, you can toggle NPCs off --- game.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++----------- game.h | 1 + npc.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++--- npc.h | 20 +++++++----- npcmove.cpp | 17 +++++++---- npctalk.cpp | 14 +++++++-- 6 files changed, 178 insertions(+), 39 deletions(-) diff --git a/game.cpp b/game.cpp index 8868cfa1a0..0be010a81f 100644 --- a/game.cpp +++ b/game.cpp @@ -69,6 +69,13 @@ game::game() curmes = 0; // We haven't read any messages yet uquit = QUIT_NO; // We haven't quit the game debugmon = false; // We're not printing debug messages + no_npc = false; // We're not suppressing NPC spawns + +// ... Unless data/no_npc.txt exists. + std::ifstream ifile("data/no_npc.txt"); + if (ifile) + no_npc = true; + weather = WEATHER_CLEAR; // Start with some nice weather... nextweather = MINUTES(STARTING_MINUTES + 30); // Weather shift in 30 turnssincelastmon = 0; //Auto safe mode init @@ -1120,7 +1127,8 @@ void game::get_input() char ch = input(); // See keypress.h - translates keypad and arrows to vikeys if (keymap.find(ch) == keymap.end()) { - add_msg("Unknown command: '%c'", ch); + if (ch != ' ' && ch != KEY_ESCAPE && ch != '\n') + add_msg("Unknown command: '%c'", ch); return; } @@ -1575,7 +1583,6 @@ bool game::load_master() int num_missions, num_npc, num_factions, num_items; fin >> num_missions; - debugmsg("missions: %d", num_missions); if (fin.peek() == '\n') fin.get(junk); // Chomp that pesky endline for (int i = 0; i < num_missions; i++) { @@ -1585,7 +1592,6 @@ bool game::load_master() } fin >> num_factions; - debugmsg("factions: %d", num_factions); if (fin.peek() == '\n') fin.get(junk); // Chomp that pesky endline for (int i = 0; i < num_factions; i++) { @@ -1596,7 +1602,6 @@ bool game::load_master() } // NPCs come next fin >> num_npc; - debugmsg("npcs: %d", num_npc); if (fin.peek() == '\n') fin.get(junk); // Chomp that pesky endline for (int i = 0; i < num_npc; i++) { @@ -1862,7 +1867,8 @@ void game::debug() "Spawn a vehicle", // 10 "Increase all skills", // 11 "Learn all melee styles", // 12 - "Cancel", // 13 + "Check NPC", // 13 + "Cancel", // 14 NULL); int veh_num; std::vector opts; @@ -1896,11 +1902,19 @@ void game::debug() case 5: { npc temp; + temp.normalize(this); temp.randomize(this); temp.attitude = NPCATT_TALK; temp.spawn_at(&cur_om, levx + (1 * rng(-2, 2)), levy + (1 * rng(-2, 2))); temp.posx = u.posx - 4; temp.posy = u.posy - 4; + temp.form_opinion(&u); + temp.attitude = NPCATT_TALK; + temp.mission = NPC_MISSION_NULL; + int mission_index = reserve_random_mission(ORIGIN_ANY_NPC, + om_location(), temp.id); + if (mission_index != -1) + temp.chatbin.missions.push_back(mission_index); active_npc.push_back(temp); } break; @@ -1912,12 +1926,15 @@ void game::debug() popup_top("\ Location %d:%d in %d:%d, %s\n\ Current turn: %d; Next spawn %d.\n\ +NPCs are %s spawn.\n\ %d monsters exist.\n\ %d events planned.", u.posx, u.posy, levx, levy, oterlist[cur_om.ter(levx / 2, levy / 2)].name.c_str(), -int(turn), int(nextspawn), z.size(), events.size()); +int(turn), int(nextspawn), (no_npc ? "NOT going to" : "going to"), +z.size(), events.size()); + if (!active_npc.empty()) - popup_top("\%s: %d:%d (you: %d:%d)", active_npc[0].name.c_str(), + popup_top("%s: %d:%d (you: %d:%d)", active_npc[0].name.c_str(), active_npc[0].posx, active_npc[0].posy, u.posx, u.posy); break; @@ -1933,21 +1950,21 @@ int(turn), int(nextspawn), z.size(), events.size()); break; case 10: - if (m.veh_at(u.posx, u.posy).type != veh_null) { - debugmsg ("There's already vehicle here"); - break; + if (m.veh_at(u.posx, u.posy).type != veh_null) + debugmsg ("There's already vehicle here"); + else { + for (int i = 2; i < vtypes.size(); i++) + opts.push_back (vtypes[i]->name); + opts.push_back (std::string("Cancel")); + veh_num = menu_vec ("Choose vehicle to spawn", opts) + 1; + if (veh_num > 1 && veh_num < num_vehicles) + m.add_vehicle (this, (vhtype_id)veh_num, u.posx, u.posy, -90); } - for (int i = 2; i < vtypes.size(); i++) - opts.push_back (vtypes[i]->name); - opts.push_back (std::string("Cancel")); - veh_num = menu_vec ("Choose vehicle to spawn", opts) + 1; - if (veh_num > 1 && veh_num < num_vehicles) - m.add_vehicle (this, (vhtype_id)veh_num, u.posx, u.posy, -90); break; case 11: for (int i = 0; i < num_skill_types; i++) - u.sklevel[i]++; + u.sklevel[i] += 3; break; case 12: @@ -1955,6 +1972,41 @@ int(turn), int(nextspawn), z.size(), events.size()); u.styles.push_back( itype_id(i) ); break; + case 13: { + point p = look_around(); + int npcdex = npc_at(p.x, p.y); + if (npcdex == -1) + popup("No NPC there."); + else { + std::stringstream data; + npc *p = &(active_npc[npcdex]); + data << p->name << " " << (p->male ? "Male" : "Female") << std::endl; + data << npc_class_name(p->myclass) << "; " << + npc_attitude_name(p->attitude) << std::endl; + if (p->has_destination()) + data << "Destination: " << p->goalx << ":" << p->goaly << "(" << + oterlist[ cur_om.ter(p->goalx, p->goaly) ].name << ")" << + std::endl; + else + data << "No destination." << std::endl; + data << "Trust: " << p->op_of_u.trust << " Fear: " << p->op_of_u.fear << + " Value: " << p->op_of_u.value << " Anger: " << p->op_of_u.anger << + " Owed: " << p->op_of_u.owed << std::endl; + data << "Aggression: " << int(p->personality.aggression) << " Bravery: " << + int(p->personality.bravery) << " Collector: " << + int(p->personality.collector) << " Altruism: " << + int(p->personality.altruism) << std::endl; + for (int i = 0; i < num_skill_types; i++) { + data << skill_name( skill(i) ) << ": " << p->sklevel[i]; + if (i % 2 == 1) + data << std::endl; + else + data << "\t"; + } + + full_screen_popup(data.str().c_str()); + } + } break; } erase(); refresh_all(); @@ -6521,7 +6573,7 @@ void game::spawn_mon(int shiftx, int shifty) int iter; int t; // Create a new NPC? - if (one_in(50 + 5 * cur_om.npcs.size())) { + if (!no_npc && one_in(100 + 15 * cur_om.npcs.size())) { npc tmp; tmp.normalize(this); tmp.randomize(this); diff --git a/game.h b/game.h index 5952c9fcfb..f2a99b74fd 100644 --- a/game.h +++ b/game.h @@ -198,6 +198,7 @@ class game std::vector items_dragged; int weight_dragged; // Computed once, when you start dragging bool debugmon; + bool no_npc; // Display data... TODO: Make this more portable? WINDOW *w_terrain; WINDOW *w_minimap; diff --git a/npc.cpp b/npc.cpp index 95a9b542fb..0603b61c91 100644 --- a/npc.cpp +++ b/npc.cpp @@ -368,8 +368,8 @@ void npc::randomize(game *g, npc_class type) weapon = item(g->itypes[0], 0); inv.clear(); personality.aggression = rng(-10, 10); - personality.bravery = rng(-10, 10); - personality.collector = rng(-10, 10); + personality.bravery = rng( -3, 10); + personality.collector = rng( -1, 10); personality.altruism = rng(-10, 10); //cash = 100 * rng(0, 20) + 10 * rng(0, 30) + rng(0, 50); cash = 0; @@ -1361,7 +1361,7 @@ void npc::form_opinion(player *u) op_of_u.value += 2; } - if (op_of_u.fear < 2 * personality.bravery + 3 && + if (op_of_u.fear < personality.bravery + 10 && op_of_u.fear - personality.aggression > -10 && op_of_u.trust > -8) attitude = NPCATT_TALK; else if (op_of_u.fear - 2 * personality.aggression - personality.bravery < -30) @@ -1372,7 +1372,7 @@ void npc::form_opinion(player *u) talk_topic npc::pick_talk_topic(player *u) { - form_opinion(u); + //form_opinion(u); if (personality.aggression > 0) { if (op_of_u.fear * 2 < personality.bravery && personality.altruism < 0) return TALK_MUG; @@ -1688,7 +1688,7 @@ int npc::value(item &it) if (inv[i].is_gun()) { gun = dynamic_cast(inv[i].type); if (ammo->type == gun->ammo) - ret += 6; + ret += 14; } } } @@ -2149,3 +2149,70 @@ std::string random_last_name() fin.close(); return lastname; } + +std::string npc_attitude_name(npc_attitude att) +{ + switch (att) { + case NPCATT_NULL: // Don't care/ignoring player + return "Ignoring"; + case NPCATT_TALK: // Move to and talk to player + return "Wants to talk"; + case NPCATT_TRADE: // Move to and trade with player + return "Wants to trade"; + case NPCATT_FOLLOW: // Follow the player + return "Following"; + case NPCATT_FOLLOW_RUN: // Follow the player, don't shoot monsters + return "Following & ignoring monsters"; + case NPCATT_LEAD: // Lead the player, wait for them if they're behind + return "Leading"; + case NPCATT_WAIT: // Waiting for the player + return "Waiting for you"; + case NPCATT_DEFEND: // Kill monsters that threaten the player + return "Defending you"; + case NPCATT_MUG: // Mug the player + return "Mugging you"; + case NPCATT_WAIT_FOR_LEAVE: // Attack the player if our patience runs out + return "Waiting for you to leave"; + case NPCATT_KILL: // Kill the player + return "Attacking to kill"; + case NPCATT_FLEE: // Get away from the player + return "Fleeing"; + case NPCATT_SLAVE: // Following the player under duress + return "Enslaved"; + case NPCATT_HEAL: // Get to the player and heal them + return "Healing you"; + + case NPCATT_MISSING: // Special; missing NPC as part of mission + return "Missing NPC"; + case NPCATT_KIDNAPPED: // Special; kidnapped NPC as part of mission + return "Kidnapped"; + default: + return "Unknown"; + } + return "Unknown"; +} + +std::string npc_class_name(npc_class classtype) +{ + switch(classtype) { + case NC_NONE: + return "No class"; + case NC_SHOPKEEP: // Found in towns. Stays in his shop mostly. + return "Shopkeep"; + case NC_HACKER: // Weak in combat but has hacking skills and equipment + return "Hacker"; + case NC_DOCTOR: // Found in towns, or roaming. Stays in the clinic. + return "Doctor"; + case NC_TRADER: // Roaming trader, journeying between towns. + return "Trader"; + case NC_NINJA: // Specializes in unarmed combat, carries few items + return "Ninja"; + case NC_COWBOY: // Gunslinger and survivalist + return "Cowboy"; + case NC_SCIENTIST: // Uses intelligence-based skills and high-tech items + return "Scientist"; + case NC_BOUNTY_HUNTER: // Resourceful and well-armored + return "Bounty Hunter"; + } + return "Unknown class"; +} diff --git a/npc.h b/npc.h index dab37078ba..5ecc484d6d 100644 --- a/npc.h +++ b/npc.h @@ -52,6 +52,8 @@ enum npc_attitude { NPCATT_MAX }; +std::string npc_attitude_name(npc_attitude); + enum npc_mission { NPC_MISSION_NULL = 0, // Nothing in particular NPC_MISSION_RESCUE_U, // Find the player and aid them @@ -63,6 +65,8 @@ enum npc_mission { NUM_NPC_MISSIONS }; +//std::string npc_mission_name(npc_mission); + enum npc_class { NC_NONE, NC_SHOPKEEP, // Found in towns. Stays in his shop mostly. @@ -76,17 +80,19 @@ enum npc_class { NC_MAX }; +std::string npc_class_name(npc_class); + enum npc_action { npc_undecided = 0, - npc_pause, - npc_reload, npc_sleep, - npc_pickup, + npc_pause, //1 + npc_reload, npc_sleep, // 2, 3 + npc_pickup, // 4 npc_escape_item, npc_wield_melee, npc_wield_loaded_gun, npc_wield_empty_gun, - npc_heal, npc_use_painkiller, npc_eat, npc_drop_items, - npc_flee, npc_melee, npc_shoot, npc_shoot_burst, npc_alt_attack, + npc_heal, npc_use_painkiller, npc_eat, npc_drop_items, // 5 - 12 + npc_flee, npc_melee, npc_shoot, npc_shoot_burst, npc_alt_attack, // 13 - 17 npc_look_for_player, npc_heal_player, npc_follow_player, npc_talk_to_player, - npc_mug_player, - npc_goto_destination, npc_avoid_friendly_fire, + npc_mug_player, // 18 - 22 + npc_goto_destination, npc_avoid_friendly_fire, // 23, 24 num_npc_actions }; diff --git a/npcmove.cpp b/npcmove.cpp index 723ca1ba4a..f3c76b8c99 100644 --- a/npcmove.cpp +++ b/npcmove.cpp @@ -47,7 +47,8 @@ void npc::move(game *g) choose_monster_target(g, target, danger, total_danger); if (g->debugmon) - debugmsg("NPC %s: target = %d, danger = %d", name.c_str(), target, danger); + debugmsg("NPC %s: target = %d, danger = %d, range = %d", + name.c_str(), target, danger, confident_range(-1)); if (is_enemy()) { int pl_danger = player_danger( &(g->u) ); @@ -287,6 +288,7 @@ void npc::execute_action(game *g, npc_action action, int target) case npc_talk_to_player: talk_to_u(g); + moves = 0; break; case npc_mug_player: @@ -312,7 +314,7 @@ void npc::execute_action(game *g, npc_action action, int target) } if (oldmoves == moves) { - debugmsg("NPC didn't use its moves. Turning on debug mode."); + debugmsg("NPC didn't use its moves. Action %d. Turning on debug mode.", action); g->debugmon = true; } } @@ -672,7 +674,7 @@ void npc::use_escape_item(game *g, int index, int target) int npc::confident_range(int index) { - if (index == -1 || !weapon.is_gun() || weapon.charges <= 0) + if (index == -1 && (!weapon.is_gun() || weapon.charges <= 0)) return 1; double deviation = 0; @@ -711,7 +713,7 @@ int npc::confident_range(int index) debugmsg("%s has NULL curammo!", name.c_str()); // TODO: investigate this bug else { deviation += .5 * weapon.curammo->accuracy; - max = weapon.curammo->range; + max = weapon.range(); } deviation += .5 * firing->accuracy; deviation += 3 * recoil; @@ -744,9 +746,9 @@ int npc::confident_range(int index) // Using 180 for now for extra-confident NPCs. int ret = (max > int(180 / deviation) ? max : int(180 / deviation)); - if (weapon.is_gun() && weapon.charges > 0 && ret > weapon.curammo->range) + if (ret > weapon.curammo->range) return weapon.curammo->range; - return (max > int(180 / deviation) ? max : int(180 / deviation)); + return ret; } // Index defaults to -1, i.e., wielded weapon @@ -779,12 +781,14 @@ bool npc::wont_hit_friend(game *g, int tarx, int tary, int index) } */ // Hit an NPC that's on our team? +/* for (int n = 0; n < g->active_npc.size(); n++) { npc* guy = &(g->active_npc[n]); if (guy != this && (is_friend() == guy->is_friend()) && guy->posx == x && guy->posy == y) return false; } +*/ } } } @@ -1783,6 +1787,7 @@ void npc::mug_player(game *g, player &mark) attitude = NPCATT_FLEE; if (!one_in(3)) say(g, ""); + moves -= 100; } else { int t; bool u_see_me = g->u_see(posx, posy, t), diff --git a/npctalk.cpp b/npctalk.cpp index f85fc5ec74..b253d5d591 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -97,10 +97,8 @@ void npc::talk_to_u(game *g) } } - if (d.topic_stack.back() == TALK_NONE) { + if (d.topic_stack.back() == TALK_NONE) d.topic_stack.back() = pick_talk_topic(&(g->u)); - return; - } moves -= 100; decide_needs(); @@ -762,6 +760,13 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) SELECT_TEMP("More...", shift + 9); SUCCESS(TALK_TRAIN); } + if (shift > 0) { + int newshift = shift - 9; + if (newshift < 0) + newshift = 0; + SELECT_TEMP("Back...", newshift); + SUCCESS(TALK_TRAIN); + } RESPONSE("Eh, never mind."); SUCCESS(TALK_NONE); } break; @@ -1336,16 +1341,19 @@ void talk_function::deny_equipment(game *g, npc *p) void talk_function::hostile(game *g, npc *p) { + g->add_msg("%s turns hostile!", p->name.c_str()); p->attitude = NPCATT_KILL; } void talk_function::flee(game *g, npc *p) { + g->add_msg("%s turns to flee!", p->name.c_str()); p->attitude = NPCATT_FLEE; } void talk_function::leave(game *g, npc *p) { + g->add_msg("%s leaves.", p->name.c_str()); p->attitude = NPCATT_NULL; } From f99a0b0112312d0a94eba0444ad57e3f7c43e82b Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 5 Jul 2012 16:57:38 -0400 Subject: [PATCH 36/51] I don't even know, bug fixes or something --- Makefile | 4 ++-- game.cpp | 15 +++++++++++++-- game.h | 2 +- npc.cpp | 2 +- npctalk.cpp | 4 ++-- veh_interact.cpp | 2 +- veh_type.h | 46 +++++++++++++++++++++++----------------------- west.cpp | 16 +++++++++------- 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index 433a78ffda..2b3837c14d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # DEBUG is best turned on if you plan to debug in gdb -- please do! # PROFILE is for use with gprof or a similar program -- don't bother generally #WARNINGS = -Wall -Wextra -Wno-switch -Wno-sign-compare -Wno-missing-braces -Wno-unused-parameter -Wno-char-subscripts -#DEBUG = -g +DEBUG = -g3 -ggdb #PROFILE = -pg ODIR = obj @@ -14,7 +14,7 @@ TARGET = cataclysm OS = $(shell uname -o) CXX = g++ -CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) +CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) --std=c++0x ifeq ($(OS), Msys) LDFLAGS = -static -lpdcurses diff --git a/game.cpp b/game.cpp index 0be010a81f..6d06316d83 100644 --- a/game.cpp +++ b/game.cpp @@ -847,6 +847,7 @@ void game::cancel_activity_query(const char* message, ...) doit = true; break; case ACT_BUILD: + case ACT_VEHICLE: if (query_yn("%s Stop construction?", s.c_str())) doit = true; break; @@ -1638,7 +1639,7 @@ bool game::load_master() return true; } -void game::load(std::string name) +bool game::load(std::string name) { std::ifstream fin; std::stringstream playerfile; @@ -1647,7 +1648,7 @@ void game::load(std::string name) // First, read in basic game state information. if (!fin.is_open()) { debugmsg("No save game exists!"); - return; + return false; } u = player(); u.name = name; @@ -1728,6 +1729,8 @@ void game::load(std::string name) load_master(); set_adjacent_overmaps(true); draw(); + + return true; } void game::save() @@ -6508,6 +6511,14 @@ point game::om_location() return ret; } +point game::global_location() +{ + point ret; + ret.x = cur_om.posx*OMAPX + om_location().x; + ret.y = cur_om.posy*OMAPY + om_location().y; + return ret; +} + void game::replace_stair_monsters() { for (int i = 0; i < coming_to_stairs.size(); i++) diff --git a/game.h b/game.h index 733ef3c943..d7507fa8b2 100644 --- a/game.h +++ b/game.h @@ -211,7 +211,7 @@ class game // Game-start procedures bool opening_screen();// Warn about screen size, then present the main menu bool load_master(); // Load the master data file, with factions &c - void load(std::string name); // Load a player-specific save file + bool load(std::string name); // Load a player-specific save file void start_game(); // Starts a new game void start_special_game(special_game_id gametype); // See gamemode.cpp diff --git a/npc.cpp b/npc.cpp index 0603b61c91..2374194b93 100644 --- a/npc.cpp +++ b/npc.cpp @@ -1227,13 +1227,13 @@ bool npc::wield(game *g, int index) debugmsg("npc::wield(%d) [styles.size() = %d]", index, styles.size()); return false; } - weapon.make( g->itypes[styles[index]] ); if (volume_carried() + weapon.volume() <= volume_capacity()) { i_add(remove_weapon()); moves -= 15; } else // No room for weapon, so we drop it g->m.add_item(posx, posy, remove_weapon()); moves -= 15; + weapon.make( g->itypes[styles[index]] ); int linet; if (g->u_see(posx, posy, linet)) g->add_msg("%s assumes a %s stance.", name.c_str(), weapon.tname().c_str()); diff --git a/npctalk.cpp b/npctalk.cpp index b253d5d591..b68357672a 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -1896,8 +1896,8 @@ Tab key to switch lists, letters to pick items, Enter to finalize, Esc to quit\n } g->u.practice(sk_barter, practice / 2); p->inv = newinv; - g->u.cash += cash; - p->cash -= cash; + // g->u.cash += cash; + // p->cash -= cash; } werase(w_head); werase(w_you); diff --git a/veh_interact.cpp b/veh_interact.cpp index f6151ad63d..627b213c4e 100644 --- a/veh_interact.cpp +++ b/veh_interact.cpp @@ -273,7 +273,7 @@ void veh_interact::do_repair(int reason) wrefresh (w_parts); werase (w_msg); bool has_comps = true; - int dif = veh->part_info(sel_part).difficulty + (veh->parts[sel_part].hp <= 0? 0 : 2); + int dif = veh->part_info(sel_part).difficulty + (veh->parts[sel_part].hp <= 0? 0 : 1); bool has_skill = dif <= g->u.sklevel[sk_mechanics]; mvwprintz(w_msg, 0, 1, c_ltgray, "You need level %d skill in mechanics.", dif); mvwprintz(w_msg, 0, 16, has_skill? c_ltgreen : c_red, "%d", dif); diff --git a/veh_type.h b/veh_type.h index 02caefbee1..a977626933 100644 --- a/veh_type.h +++ b/veh_type.h @@ -140,7 +140,7 @@ const vpart_info vpart_list[num_vparts] = { // name sym color sym_b color_b dmg dur par1 par2 item { "null part", '?', c_red, '?', c_red, 100, 100, 0, 0, itm_null, 0, 0 }, - { "seat", '#', c_red, '*', c_red, 60, 300, 0, 0, itm_seat, 1, + { "seat", '#', c_red, '*', c_red, 60, 300, 0, 0, itm_seat, 0, mfb(vpf_over) | mfb(vpf_seat) | mfb(vpf_no_reinforce) }, { "frame", 'h', c_ltgray, '#', c_ltgray, 100, 400, 0, 0, itm_frame, 1, mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) }, @@ -178,73 +178,73 @@ const vpart_info vpart_list[num_vparts] = mfb(vpf_external) | mfb(vpf_mount_point) | mfb (vpf_mount_inner) | mfb(vpf_opaque) | mfb(vpf_obstacle) }, { "roof", '#', c_ltgray, '#', c_dkgray, 100, 1000, 0, 0, itm_steel_plate, 1, mfb(vpf_internal) | mfb(vpf_roof) }, - { "door", '+', c_cyan, '&', c_cyan, 80, 200, 0, 0, itm_frame, 2, + { "door", '+', c_cyan, '&', c_cyan, 80, 200, 0, 0, itm_frame, 1, mfb(vpf_external) | mfb(vpf_obstacle) | mfb(vpf_openable) }, { "windshield", '"', c_ltcyan, '0', c_ltgray, 70, 50, 0, 0, itm_glass_sheet, 1, mfb(vpf_over) | mfb(vpf_obstacle) | mfb(vpf_no_reinforce) }, - { "blade", '-', c_white, 'x', c_white, 250, 100, 0, 0, itm_machete, 3, + { "blade", '-', c_white, 'x', c_white, 250, 100, 0, 0, itm_machete, 2, mfb(vpf_external) | mfb(vpf_unmount_on_damage) | mfb(vpf_sharp) | mfb(vpf_no_reinforce) }, - { "blade", '|', c_white, 'x', c_white, 350, 100, 0, 0, itm_machete, 3, + { "blade", '|', c_white, 'x', c_white, 350, 100, 0, 0, itm_machete, 2, mfb(vpf_external) | mfb(vpf_unmount_on_damage) | mfb(vpf_sharp) | mfb(vpf_no_reinforce) }, - { "spike", '.', c_white, 'x', c_white, 300, 100, 0, 0, itm_spear_knife, 2, + { "spike", '.', c_white, 'x', c_white, 300, 100, 0, 0, itm_spear_knife, 1, mfb(vpf_external) | mfb(vpf_unmount_on_damage) | mfb(vpf_sharp) | mfb(vpf_no_reinforce) }, // size - { "large wheel",'0', c_dkgray, 'x', c_ltgray, 50, 300, 30, 0, itm_big_wheel, 3, + { "large wheel",'0', c_dkgray, 'x', c_ltgray, 50, 300, 30, 0, itm_big_wheel, 2, mfb(vpf_external) | mfb (vpf_mount_over) | mfb(vpf_wheel) | mfb(vpf_mount_point) }, { "wheel", 'o', c_dkgray, 'x', c_ltgray, 50, 200, 10, 0, itm_wheel, 2, mfb(vpf_external) | mfb (vpf_mount_over) | mfb(vpf_wheel) | mfb(vpf_mount_point) }, // power type - { "1L combustion engine", '*', c_ltred, '#', c_red, 80, 200, 120, AT_GAS, itm_combustion_small, 3, + { "1L combustion engine", '*', c_ltred, '#', c_red, 80, 200, 120, AT_GAS, itm_combustion_small, 2, mfb(vpf_internal) | mfb(vpf_engine) }, - { "2.5L combustion engine", '*', c_ltred, '#', c_red, 80, 300, 300, AT_GAS, itm_combustion, 4, + { "2.5L combustion engine", '*', c_ltred, '#', c_red, 80, 300, 300, AT_GAS, itm_combustion, 3, mfb(vpf_internal) | mfb(vpf_engine) }, - { "6L combustion engine", '*', c_ltred, '#', c_red, 80, 400, 800, AT_GAS, itm_combustion_large, 5, + { "6L combustion engine", '*', c_ltred, '#', c_red, 80, 400, 800, AT_GAS, itm_combustion_large, 4, mfb(vpf_internal) | mfb(vpf_engine) }, - { "electric motor", '*', c_yellow, '#', c_red, 80, 200, 70, AT_BATT, itm_motor, 3, + { "electric motor", '*', c_yellow, '#', c_red, 80, 200, 70, AT_BATT, itm_motor, 2, mfb(vpf_internal) | mfb(vpf_engine) }, - { "large electric motor", '*', c_yellow, '#', c_red, 80, 400, 350, AT_BATT, itm_motor_large, 4, + { "large electric motor", '*', c_yellow, '#', c_red, 80, 400, 350, AT_BATT, itm_motor_large, 3, mfb(vpf_internal) | mfb(vpf_engine) }, - { "plasma engine", '*', c_ltblue, '#', c_red, 80, 250, 400, AT_PLASMA, itm_plasma_engine, 8, + { "plasma engine", '*', c_ltblue, '#', c_red, 80, 250, 400, AT_PLASMA, itm_plasma_engine, 5, mfb(vpf_internal) | mfb(vpf_engine) }, // capacity type { "gasoline tank", 'O', c_ltred, '#', c_red, 80, 150, 3000, AT_GAS, itm_metal_tank, 1, mfb(vpf_internal) | mfb(vpf_fuel_tank) }, { "storage battery", 'O', c_yellow, '#', c_red, 80, 300, 1000, AT_BATT, itm_storage_battery, 2, mfb(vpf_internal) | mfb(vpf_fuel_tank) }, - { "minireactor", 'O', c_ltgreen, '#', c_red, 80, 700, 10000, AT_PLUT, itm_minireactor, 7, + { "minireactor", 'O', c_ltgreen, '#', c_red, 80, 700, 10000, AT_PLUT, itm_minireactor, 5, mfb(vpf_internal) | mfb(vpf_fuel_tank) }, { "hydrogene tank", 'O', c_ltblue, '#', c_red, 80, 150, 3000, AT_PLASMA, itm_metal_tank, 1, mfb(vpf_internal) | mfb(vpf_fuel_tank) }, - { "trunk", 'H', c_brown, '#', c_brown, 80, 300, 400, 0, itm_frame, 1, + { "trunk", 'H', c_brown, '#', c_brown, 80, 300, 400, 0, itm_frame, 0, mfb(vpf_over) | mfb(vpf_cargo) }, - { "box", 'o', c_brown, '#', c_brown, 60, 100, 400, 0, itm_frame, 1, + { "box", 'o', c_brown, '#', c_brown, 60, 100, 400, 0, itm_frame, 0, mfb(vpf_over) | mfb(vpf_cargo) }, { "controls", '$', c_ltgray, '$', c_red, 10, 250, 0, 0, itm_vehicle_controls, 3, mfb(vpf_internal) | mfb(vpf_controls) }, // bonus - { "muffler", '/', c_ltgray, '/', c_ltgray, 10, 150, 40, 0, itm_muffler, 2, + { "muffler", '/', c_ltgray, '/', c_ltgray, 10, 150, 40, 0, itm_muffler, 1, mfb(vpf_internal) | mfb(vpf_muffler) }, { "seatbelt", ',', c_ltgray, ',', c_red, 10, 200, 25, 0, itm_rope_6, 1, mfb(vpf_internal) | mfb(vpf_seatbelt) }, - { "solar panel", '#', c_yellow, 'x', c_yellow, 10, 20, 30, 0, itm_solar_panel, 6, + { "solar panel", '#', c_yellow, 'x', c_yellow, 10, 20, 30, 0, itm_solar_panel, 3, mfb(vpf_over) | mfb(vpf_solar_panel) }, - { "mounted M249", 't', c_cyan, '#', c_cyan, 80, 400, 0, AT_223, itm_m249, 7, + { "mounted M249", 't', c_cyan, '#', c_cyan, 80, 400, 0, AT_223, itm_m249, 4, mfb(vpf_over) | mfb(vpf_turret) | mfb(vpf_cargo) }, - { "mounted flamethrower", 't', c_dkgray, '#', c_dkgray, 80, 400, 0, AT_GAS, itm_flamethrower, 8, + { "mounted flamethrower", 't', c_dkgray, '#', c_dkgray, 80, 400, 0, AT_GAS, itm_flamethrower, 5, mfb(vpf_over) | mfb(vpf_turret) }, - { "mounted plasma gun", 't', c_ltblue, '#', c_ltblue, 80, 400, 0, AT_PLASMA, itm_plasma_rifle, 9, + { "mounted plasma gun", 't', c_ltblue, '#', c_ltblue, 80, 400, 0, AT_PLASMA, itm_plasma_rifle, 6, mfb(vpf_over) | mfb(vpf_turret) }, - { "steel plating", ')', c_ltcyan, ')', c_ltcyan, 100, 1000, 0, 0, itm_steel_plate, 3, + { "steel plating", ')', c_ltcyan, ')', c_ltcyan, 100, 1000, 0, 0, itm_steel_plate, 2, mfb(vpf_internal) | mfb(vpf_armor) }, - { "superalloy plating",')', c_dkgray, ')', c_dkgray, 100, 900, 0, 0, itm_alloy_plate, 5, + { "superalloy plating",')', c_dkgray, ')', c_dkgray, 100, 900, 0, 0, itm_alloy_plate, 4, mfb(vpf_internal) | mfb(vpf_armor) }, { "spiked plating", ')', c_red, ')', c_red, 150, 900, 0, 0, itm_spiked_plate, 3, mfb(vpf_internal) | mfb(vpf_armor) | mfb(vpf_sharp) }, - { "hard plating", ')', c_cyan, ')', c_cyan, 100, 2300, 0, 0, itm_hard_plate, 5, + { "hard plating", ')', c_cyan, ')', c_cyan, 100, 2300, 0, 0, itm_hard_plate, 3, mfb(vpf_internal) | mfb(vpf_armor) } }; diff --git a/west.cpp b/west.cpp index 1efab94d4b..f1788df712 100644 --- a/west.cpp +++ b/west.cpp @@ -14,12 +14,14 @@ bool west_game::init (game *g) return false; if (g->load(g->u.name)) { - if(g->u.cash == 0) - horde_location = g->global_location().x - 10; - else - horde_location = g->u.cash; + if(g->u.scent == 500) + horde_location = g->global_location().x - 25; + else { + horde_location = g->u.scent - 500; + g->u.scent = 500; + } - // g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); + g->add_msg("The horde is %d map squares away.", distance_to_horde(g)); popup_top("The horde comes."); return true; @@ -68,7 +70,7 @@ bool west_game::init (game *g) g->u.normalize(g); g->u.weapon = item(g->itypes[itm_baton], 0, 'a' + g->u.worn.size()); - horde_location = g->global_location().x - 10; + horde_location = g->global_location().x - 25; return true; } @@ -136,6 +138,6 @@ void west_game::per_turn(game *g) void west_game::pre_action(game *g, action_id &act) { if (act == ACTION_SAVE) { - g->u.cash = horde_location; + g->u.scent = 500 + horde_location; } } From 785d64ad0279537d01120010b5420c59a7cc7586 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 5 Jul 2012 17:12:14 -0400 Subject: [PATCH 37/51] Inventory sorts itself --- player.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/player.cpp b/player.cpp index c8b1d9f2a7..51625e43d8 100644 --- a/player.cpp +++ b/player.cpp @@ -9,6 +9,7 @@ #include "inventory.h" #include "artifact.h" #include +#include #include #if (defined _WIN32 || defined WINDOWS) @@ -2936,6 +2937,11 @@ void player::add_morale(morale_type type, int bonus, int max_bonus, } } +bool sort_fn (std::vector i, std::vector j) +{ + return i[0].type->id < j[0].type->id; +} + void player::sort_inv() { // guns ammo weaps armor food tools books other @@ -2962,6 +2968,7 @@ void player::sort_inv() } inv.clear(); for (int i = 0; i < 8; i++) { + std::stable_sort(types[i].begin(), types[i].end(), sort_fn); for (int j = 0; j < types[i].size(); j++) inv.push_back(types[i][j]); } From 7fbd1e152e4427619502fa8c829e0a521af5bbf9 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Fri, 6 Jul 2012 13:40:57 -0400 Subject: [PATCH 38/51] Bug fixes through #91 in forum thread, rebalanced vehicle requirements + xp gain --- dialogue.h | 4 ++++ iuse.cpp | 2 +- mapgen.cpp | 4 ++-- monmove.cpp | 3 ++- npcmove.cpp | 2 +- omdata.h | 2 +- overmap.cpp | 14 +++++++------- veh_interact.cpp | 6 +++--- wish.cpp | 1 + 9 files changed, 22 insertions(+), 16 deletions(-) diff --git a/dialogue.h b/dialogue.h index 1dd749ffa9..449d8d6e9b 100644 --- a/dialogue.h +++ b/dialogue.h @@ -104,6 +104,8 @@ struct talk_response tempvalue = -1; effect_success = &talk_function::nothing; effect_failure = &talk_function::nothing; + opinion_success = npc_opinion(); + opinion_failure = npc_opinion(); success = TALK_NONE; failure = TALK_NONE; } @@ -118,6 +120,8 @@ struct talk_response tempvalue = rhs.tempvalue; effect_success = rhs.effect_success; effect_failure = rhs.effect_failure; + opinion_success = rhs.opinion_success; + opinion_failure = rhs.opinion_failure; success = rhs.success; failure = rhs.failure; } diff --git a/iuse.cpp b/iuse.cpp index 0b109ba334..81435d0412 100644 --- a/iuse.cpp +++ b/iuse.cpp @@ -913,7 +913,7 @@ void iuse::hammer(game *g, player *p, item *it, bool t) void iuse::light_off(game *g, player *p, item *it, bool t) { if (it->charges == 0) - g->add_msg("The flaslight's batteries are dead."); + g->add_msg("The flashlight's batteries are dead."); else { g->add_msg("You turn the flashlight on."); it->make(g->itypes[itm_flashlight_on]); diff --git a/mapgen.cpp b/mapgen.cpp index 1219f6e336..5714cb0cb2 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -6975,9 +6975,9 @@ void science_room(map *m, int x1, int y1, int x2, int y2, int rotate) int compx = int((x1 + x2) / 2), compy = int((y1 + y2) / 2); m->ter(compx, compy) = t_console; - computer* tmpcomp = m->add_computer(compx, compy, "Bionic access", 4); + computer* tmpcomp = m->add_computer(compx, compy, "Bionic access", 2); tmpcomp->add_option("Manifest", COMPACT_LIST_BIONICS, 0); - tmpcomp->add_option("Open Chambers", COMPACT_RELEASE, 2); + tmpcomp->add_option("Open Chambers", COMPACT_RELEASE, 3); tmpcomp->add_failure(COMPFAIL_MANHACKS); tmpcomp->add_failure(COMPFAIL_SECUBOTS); } diff --git a/monmove.cpp b/monmove.cpp index afa76384e0..382c5de6d4 100644 --- a/monmove.cpp +++ b/monmove.cpp @@ -548,7 +548,8 @@ void monster::hit_player(game *g, player &p, bool can_grab) npc* tmp = dynamic_cast(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); - g->active_npc.erase(g->active_npc.begin() + index); + if(index != -1) + g->active_npc.erase(g->active_npc.begin() + index); plans.clear(); } } diff --git a/npcmove.cpp b/npcmove.cpp index f3c76b8c99..3840e15f1b 100644 --- a/npcmove.cpp +++ b/npcmove.cpp @@ -844,7 +844,7 @@ void npc::update_path(game *g, int x, int y) if (last.x == x && last.y == y) return; // Our path already leads to that point, no need to recalculate path = g->m.route(posx, posy, x, y); - if (path[0].x == posx && path[0].y == posy) + if (!path.empty() && path[0].x == posx && path[0].y == posy) path.erase(path.begin()); } diff --git a/omdata.h b/omdata.h index cec98764c2..0141983389 100644 --- a/omdata.h +++ b/omdata.h @@ -425,7 +425,7 @@ const overmap_special overmap_specials[NUM_OMSPECS] = { {ot_crater, 0, 10, 0, -1, mcat_null, 0, 0, 0, 0, &omspec_place::land, mfb(OMS_FLAG_BLOB)}, -{ot_hive, 0, 50, 10, -1, mcat_bee, 20, 60, 2, 4, +{ot_hive, 0, 5, 10, -1, mcat_bee, 20, 60, 2, 4, &omspec_place::forest, mfb(OMS_FLAG_3X3)}, {ot_house_north, 0,100, 0, -1, mcat_null, 0, 0, 0, 0, diff --git a/overmap.cpp b/overmap.cpp index ee7a17ba20..b8f15dad7a 100644 --- a/overmap.cpp +++ b/overmap.cpp @@ -1813,15 +1813,15 @@ void overmap::polish(oter_id min, oter_id max) good_road(ot_ants_ns, x, y); else if (ter(x, y) >= ot_river_center && ter(x, y) < ot_river_nw) good_river(x, y); - } // Sometimes a bridge will start at the edge of a river, and this looks ugly // So, fix it by making that square normal road; bit of a kludge but it works - else if (ter(x, y) == ot_bridge_ns && - (!is_river(ter(x - 1, y)) || !is_river(ter(x + 1, y)))) - ter(x, y) = ot_road_ns; - else if (ter(x, y) == ot_bridge_ew && - (!is_river(ter(x, y - 1)) || !is_river(ter(x, y + 1)))) - ter(x, y) = ot_road_ew; + else if (ter(x, y) == ot_bridge_ns && + (!is_river(ter(x - 1, y)) || !is_river(ter(x + 1, y)))) + ter(x, y) = ot_road_ns; + else if (ter(x, y) == ot_bridge_ew && + (!is_river(ter(x, y - 1)) || !is_river(ter(x, y + 1)))) + ter(x, y) = ot_road_ew; + } } } // Fixes stretches of parallel roads--turns them into two-lane highways diff --git a/veh_interact.cpp b/veh_interact.cpp index 627b213c4e..a2d1ff0ae3 100644 --- a/veh_interact.cpp +++ b/veh_interact.cpp @@ -644,7 +644,7 @@ void complete_vehicle (game *g) consume_tools(g, tools); g->add_msg ("You install %s into %s.", vpart_list[part].name, veh.name.c_str()); - g->u.practice (sk_mechanics, vpart_list[part].difficulty * 5 + 20); + g->u.practice (sk_mechanics, vpart_list[part].difficulty * 5); break; case 'r': if (veh.parts[part].hp <= 0) @@ -663,7 +663,7 @@ void complete_vehicle (game *g) veh.parts[part].hp = veh.part_info(part).durability; g->add_msg ("You repair %s's %s.", veh.name.c_str(), veh.part_info(part).name); - g->u.practice (sk_mechanics, (vpart_list[part].difficulty + dd) * 5 + 20); + g->u.practice (sk_mechanics, (vpart_list[part].difficulty + dd) * 5); break; case 'f': if (!g->pl_refill_vehicle(veh, part, true)) @@ -689,7 +689,7 @@ void complete_vehicle (game *g) } if (!broken) g->m.add_item (g->u.posx, g->u.posy, g->itypes[itm], g->turn); - g->u.practice (sk_mechanics, 2 * 5 + 20); + g->u.practice (sk_mechanics, 2 * 5); break; default:; diff --git a/wish.cpp b/wish.cpp index 27f3f0091f..2b5fbba288 100644 --- a/wish.cpp +++ b/wish.cpp @@ -135,6 +135,7 @@ void game::wish() wprintz(w_list, itypes[i-1+shift]->color, "%c%", itypes[i-1+shift]->sym); } tmp.make(itypes[a + shift]); + tmp.bday = turn; if (tmp.is_tool()) tmp.charges = dynamic_cast(tmp.type)->max_charges; else if (tmp.is_ammo()) From adf7026ad675e71d22bfee1828991eb621ceef70 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Fri, 6 Jul 2012 17:07:46 -0400 Subject: [PATCH 39/51] Merged extended view, bugfixes --- action.cpp | 2 + action.h | 1 + enums.h | 6 ++ game.cpp | 217 +++++++++++++++++++++++++++++++++++++++++++++------ game.h | 8 +- keypress.cpp | 1 + map.cpp | 52 +++++++++--- map.h | 5 +- mapdata.h | 6 ++ monster.cpp | 17 +++- monster.h | 3 +- npc.cpp | 20 ++++- npc.h | 3 +- npctalk.cpp | 4 +- 14 files changed, 295 insertions(+), 50 deletions(-) diff --git a/action.cpp b/action.cpp index 74a6feb6d6..3eb73a3b29 100644 --- a/action.cpp +++ b/action.cpp @@ -89,6 +89,8 @@ action_id look_up_action(std::string ident) return ACTION_CHAT; if (ident == "look") return ACTION_LOOK; + if (ident == "look_surroundings") + return ACTION_LOOK_SURROUNDINGS; if (ident == "inventory") return ACTION_INVENTORY; if (ident == "organize") diff --git a/action.h b/action.h index 6f80471154..1c28900f66 100644 --- a/action.h +++ b/action.h @@ -24,6 +24,7 @@ ACTION_PICKUP, ACTION_BUTCHER, ACTION_CHAT, ACTION_LOOK, +ACTION_LOOK_SURROUNDINGS, // Inventory Interaction (including quasi-inventories like bionics) ACTION_INVENTORY, ACTION_ORGANIZE, diff --git a/enums.h b/enums.h index d466924975..0563caebd6 100644 --- a/enums.h +++ b/enums.h @@ -5,6 +5,12 @@ #define sgn(x) (((x) < 0) ? -1 : 1) #endif +enum view_mode { + NORMAL, + EXTENDED, + DEBUG +}; + enum material { MNULL = 0, //Food Materials diff --git a/game.cpp b/game.cpp index 6d06316d83..dc9267b733 100644 --- a/game.cpp +++ b/game.cpp @@ -14,6 +14,11 @@ #include #include +#if (defined _WIN32 || defined WINDOWS) + #define LINES 25 + #define COLS 80 +#endif + #define MAX_MONSTERS_MOVING 40 // Efficiency! void intro(); @@ -1251,6 +1256,10 @@ void game::get_input() look_around(); break; + case ACTION_LOOK_SURROUNDINGS: + draw_surroundings(EXTENDED); + break; + case ACTION_INVENTORY: { bool has = false; do { @@ -1450,7 +1459,7 @@ void game::get_input() break; case ACTION_DISPLAY_SCENT: - display_scent(); + debug2(); break; case ACTION_TOGGLE_DEBUGMON: @@ -2015,6 +2024,25 @@ z.size(), events.size()); refresh_all(); } +void game::debug2() +{ + int action = menu("Debug Functions - Using these is CHEATING!", + "Display scent map", // 1 + "Full size extended view", // 2 + "Cancel", // 3 + NULL); + switch (action) { + case 1: + display_scent(); + break; + case 2: + draw_surroundings(DEBUG); + break; + } + erase(); + refresh_all(); +} + void game::mondebug() { int tc; @@ -2325,8 +2353,8 @@ void game::draw() { // Draw map werase(w_terrain); - draw_ter(); - draw_footsteps(); + draw_ter(w_terrain); + draw_footsteps(w_terrain); mon_info(); // Draw Status draw_HP(); @@ -2373,53 +2401,173 @@ bool game::isBetween(int test, int down, int up) else return false; } -void game::draw_ter() +void game::draw_ter(WINDOW * w, view_mode vm, int xshift, int yshift) { int t = 0; - m.draw(this, w_terrain); + int ext_x_offset = 0; + int ext_y_offset = 0; + if(vm == EXTENDED){ + ext_x_offset = EXTX; + } + else if(vm == DEBUG){ + ext_x_offset = (COLS/2) - SEEX; + ext_y_offset = (LINES/2) - SEEY; + } + + m.draw(this, w, vm, xshift, yshift); // Draw monsters int distx, disty; for (int i = 0; i < z.size(); i++) { disty = abs(z[i].posy - u.posy); distx = abs(z[i].posx - u.posx); - if (distx <= SEEX && disty <= SEEY && u_see(&(z[i]), t)) - z[i].draw(w_terrain, u.posx, u.posy, false); - else if (z[i].has_flag(MF_WARM) && distx <= SEEX && disty <= SEEY && + if (distx <= SEEX + ext_x_offset && disty <= SEEY + ext_y_offset && u_see(&(z[i]), t)) + z[i].draw(w, u.posx, u.posy, false, vm, xshift, yshift); + else if (z[i].has_flag(MF_WARM) && distx <= SEEX + ext_x_offset && disty <= SEEY + ext_y_offset && (u.has_active_bionic(bio_infrared) || u.has_trait(PF_INFRARED))) - mvwputch(w_terrain, SEEY + z[i].posy - u.posy, SEEX + z[i].posx - u.posx, + mvwputch(w, SEEY + ext_y_offset + z[i].posy - u.posy, SEEX + ext_x_offset + z[i].posx - u.posx, c_red, '?'); } // Draw NPCs for (int i = 0; i < active_npc.size(); i++) { disty = abs(active_npc[i].posy - u.posy); distx = abs(active_npc[i].posx - u.posx); - if (distx <= SEEX && disty <= SEEY && + if (distx <= SEEX + ext_x_offset && disty <= SEEY + ext_y_offset && u_see(active_npc[i].posx, active_npc[i].posy, t)) - active_npc[i].draw(w_terrain, u.posx, u.posy, false); + active_npc[i].draw(w, u.posx, u.posy, false, vm, xshift, yshift); } if (u.has_active_bionic(bio_scent_vision)) { - for (int realx = u.posx - SEEX; realx <= u.posx + SEEX; realx++) { - for (int realy = u.posy - SEEY; realy <= u.posy + SEEY; realy++) { + for (int realx = u.posx - SEEX - ext_x_offset; realx <= u.posx + SEEX + ext_x_offset + 1; realx++) { + for (int realy = u.posy - SEEY - ext_y_offset; realy <= u.posy + SEEY + ext_y_offset + 1; realy++) { if (scent(realx, realy) != 0) { int tempx = u.posx - realx, tempy = u.posy - realy; if (!(isBetween(tempx, -2, 2) && isBetween(tempy, -2, 2))) { if (mon_at(realx, realy) != -1) - mvwputch(w_terrain, realy + SEEY - u.posy, realx + SEEX - u.posx, + mvwputch(w, realy + SEEY + ext_y_offset - u.posy, realx + SEEX + ext_x_offset - u.posx, c_white, '?'); else - mvwputch(w_terrain, realy + SEEY - u.posy, realx + SEEX - u.posx, + mvwputch(w_terrain, realy + SEEY + ext_y_offset - u.posy, realx + SEEX + ext_x_offset - u.posx, c_magenta, '#'); } } } } } - wrefresh(w_terrain); + wrefresh(w); if (u.has_disease(DI_VISUALS)) hallucinate(); } + void game::draw_surroundings(view_mode vm) + { + char ch = 0; + int xshift = 0, yshift = 0, idx = 0; + action_id act, last_act; + WINDOW* w_extmap; + if(vm == EXTENDED){ + w_extmap = newwin(25, 80, 0, 0); + } + else if(vm == DEBUG){ + w_extmap = newwin(LINES, COLS, 0, 0); + } + act = ACTION_PAUSE; + last_act = ACTION_NULL; + do { + if(act != last_act || act == ACTION_LOOK_SURROUNDINGS){ + wclear(w_extmap); + draw_ter(w_extmap, vm, xshift, yshift); + draw_footsteps(w_extmap, vm, true, xshift, yshift); + wrefresh(w_extmap); + } + ch = input(); + if (keymap.find(ch) == keymap.end()) { + continue; + } + last_act = act; + act = keymap[ch]; + if(act != last_act || act == ACTION_LOOK_SURROUNDINGS){ + switch(act){ + case ACTION_MOVE_N: //NORTH + xshift = 0; + yshift = -(SEEY - 1); + idx = 0; + break; + case ACTION_MOVE_S: //SOUTH + xshift = 0; + yshift = SEEY - 1; + idx = 0; + break; + case ACTION_MOVE_NW: //NORTHWEST + xshift = -(SEEX + EXTX - 15); + yshift = -(SEEY - 1); + idx = 0; + break; + case ACTION_MOVE_NE: //NORTHEAST + xshift = SEEX + EXTX - 15; + yshift = -(SEEY - 1); + idx = 0; + break; + case ACTION_MOVE_SW: //SOUTHWEST + xshift = -(SEEX + EXTX - 15); + yshift = SEEY - 1; + idx = 0; + break; + case ACTION_MOVE_SE: //SOUTHEAST + xshift = SEEX + EXTX - 15; + yshift = SEEY - 1; + idx = 0; + break; + case ACTION_MOVE_W: //WEST + xshift = -(SEEX + EXTX - 15); + yshift = 0; + idx = 0; + break; + case ACTION_MOVE_E: //EAST + xshift = SEEX + EXTX - 15; + yshift = 0; + idx = 0; + break; + case ACTION_PAUSE: //CENTERED + xshift = 0; + yshift = 0; + idx = 0; + break; + case ACTION_LOOK_SURROUNDINGS: + idx = (idx + 1) % 5; + switch(idx){ + case 0: //Centered + xshift = 0; + yshift = 0; + break; + case 1: //NORTHWEST + xshift = -(SEEX + EXTX - 15); + yshift = -(SEEY - 1); + break; + case 2: //NORTHEAST + xshift = SEEX + EXTX - 15; + yshift = -(SEEY - 1); + break; + case 3: //SOUTHEAST + xshift = SEEX + EXTX - 15; + yshift = SEEY - 1; + break; + case 4: //SOUTHWEST + xshift = -(SEEX + EXTX - 15); + yshift = SEEY - 1; + break; + } + break; + } + } + } while (ch != ' ' && ch != KEY_ESCAPE && ch != '\n'); + werase(w_extmap); + wrefresh(w_extmap); + delwin(w_extmap); + refresh_all(); + } + + + void game::refresh_all() { draw(); @@ -3246,14 +3394,30 @@ void game::add_footstep(int x, int y, int volume, int distance) } // draws footsteps that have been created by monsters moving about -void game::draw_footsteps() +void game::draw_footsteps(WINDOW *w, view_mode vm, bool hold, int xshift, int yshift) { - for (int i = 0; i < footsteps.size(); i++) { - mvwputch(w_terrain, SEEY + footsteps[i].y - u.posy, - SEEX + footsteps[i].x - u.posx, c_yellow, '?'); + std::vector fs; + int ext_x_offset = 0; + int ext_y_offset = 0; + if (vm == EXTENDED){ + ext_x_offset = EXTX; + fs = old_footsteps; } + else if (vm == DEBUG){ + ext_x_offset = (COLS/2) - SEEX; + ext_y_offset = (LINES/2) - SEEY; + fs = old_footsteps; + } + else + fs = footsteps; + for (int i = 0; i < fs.size(); i++) { + mvwputch(w, SEEY + ext_y_offset + fs[i].y - (u.posy + yshift), + SEEX + ext_x_offset + fs[i].x - (u.posx + xshift), c_yellow, '?'); + } + if(!hold) + old_footsteps = footsteps; footsteps.clear(); - wrefresh(w_terrain); + wrefresh(w); return; } @@ -4363,7 +4527,7 @@ shape, but with long, twisted, distended limbs."); point game::look_around() { - draw_ter(); + draw_ter(w_terrain); int lx = u.posx, ly = u.posy; int mx, my, junk; char ch; @@ -4378,7 +4542,7 @@ point game::look_around() ch = input(); if (!u_see(lx, ly, junk)) mvwputch(w_terrain, ly - u.posy + SEEY, lx - u.posx + SEEX, c_black, ' '); - draw_ter(); + draw_ter(w_terrain); get_direction(this, mx, my, ch); if (mx != -2 && my != -2) { // Directional key pressed lx += mx; @@ -4795,7 +4959,7 @@ bool game::handle_liquid(item &liquid, bool from_ground, bool infinite) else if (fuel_amnt == fuel_cap) add_msg ("Already full."); else { - veh.refill (AT_GAS, liquid.charges); + veh.refill (AT_GAS, 5*liquid.charges); add_msg ("You refill %s with %s%s.", veh.name.c_str(), veh.fuel_name(ftype).c_str(), veh.fuel_left(ftype) >= fuel_cap? " to its maximum" : ""); @@ -6190,6 +6354,7 @@ void game::vertical_move(int movez, bool force) // Figure out where we know there are up/down connectors std::vector discover; + if (!force) discover.push_back(om_location()); for (int x = 0; x < OMAPX; x++) { for (int y = 0; y < OMAPY; y++) { if (cur_om.seen(x, y) && @@ -6255,7 +6420,7 @@ void game::vertical_move(int movez, bool force) } } - set_adjacent_overmaps(); + set_adjacent_overmaps(true); refresh_all(); } @@ -6906,7 +7071,7 @@ nc_color sev(int a) void game::display_scent() { int div = query_int("Sensitivity"); - draw_ter(); + draw_ter(w_terrain); for (int x = u.posx - SEEX; x <= u.posx + SEEX; x++) { for (int y = u.posy - SEEY; y <= u.posy + SEEY; y++) { int sn = scent(x, y) / (div * 2); diff --git a/game.h b/game.h index d7507fa8b2..6090b8c44d 100644 --- a/game.h +++ b/game.h @@ -69,7 +69,8 @@ class game void save(); bool do_turn(); void draw(); - void draw_ter(); + void draw_ter(WINDOW *w, view_mode vm = NORMAL, int xshift = 0, int yshift = 0); + void draw_surroundings(view_mode vm); void advance_nextinv(); // Increment the next inventory letter void decrease_nextinv(); // Decrement the next inventory letter void add_msg(const char* msg, ...); @@ -81,8 +82,10 @@ class game // creates a list of coordinates to draw footsteps void add_footstep(int x, int y, int volume, int distance); std::vector footsteps; + std::vector old_footsteps; // visual cue to monsters moving out of the players sight - void draw_footsteps(); + void draw_footsteps(WINDOW *w, view_mode vm = NORMAL, bool hold = false, + int xshift = 0, int yshift = 0); // Explosion at (x, y) of intensity (power), with (shrapnel) chunks of shrapnel void explosion(int x, int y, int power, int shrapnel, bool fire); // Flashback at (x, y) @@ -331,6 +334,7 @@ class game // Debug functions void debug(); // All-encompassing debug screen. TODO: This. + void debug2(); void display_scent(); // Displays the scent map void mondebug(); // Debug monster behavior directly void groupdebug(); // Get into on monster groups diff --git a/keypress.cpp b/keypress.cpp index 830d046c8c..54aa58a214 100644 --- a/keypress.cpp +++ b/keypress.cpp @@ -166,6 +166,7 @@ pickup , g\n\ butcher B\n\ chat C\n\ look ; x\n\ +look_surroundings X\n\ \n\ # INVENTORY & QUASI-INVENTORY INTERACTION\n\ inventory i\n\ diff --git a/map.cpp b/map.cpp index 18512d013f..0423be5d4c 100644 --- a/map.cpp +++ b/map.cpp @@ -7,6 +7,11 @@ #include #include +#if (defined _WIN32 || defined WINDOWS) + #define LINES 25 + #define COLS 80 +#endif + #define SGN(a) (((a)<0) ? -1 : 1) #define INBOUNDS(x, y) \ (x >= 0 && x < SEEX * my_MAPSIZE && y >= 0 && y < SEEY * my_MAPSIZE) @@ -1831,33 +1836,60 @@ void map::debug() getch(); } -void map::draw(game *g, WINDOW* w) +void map::draw(game *g, WINDOW* w, view_mode vm, int xshift, int yshift) { int t = 0; + int ext_x_offset = 0; + int ext_y_offset = 0; + int xcoord, ycoord; + if(vm == NORMAL){ + xcoord = g->u.posx; + ycoord = g->u.posy; + } + else { + xcoord = g->u.posx + xshift; + ycoord = g->u.posy + yshift; + if(vm == EXTENDED){ + ext_x_offset = EXTX; + } + else if(vm == DEBUG){ + ext_x_offset = (COLS/2) - SEEX; + ext_y_offset = (LINES/2) - SEEY; + } + } int light = g->u.sight_range(g->light_level()); - for (int realx = g->u.posx - SEEX; realx <= g->u.posx + SEEX; realx++) { - for (int realy = g->u.posy - SEEY; realy <= g->u.posy + SEEY; realy++) { + for (int realx = xcoord - SEEX - ext_x_offset; realx <= xcoord + SEEX + ext_x_offset; realx++) { + for (int realy = ycoord - SEEY - ext_y_offset; realy <= ycoord + SEEY + ext_y_offset; realy++) { int dist = rl_dist(g->u.posx, g->u.posy, realx, realy); if (dist > light) { if (g->u.has_disease(DI_BOOMERED)) - mvwputch(w, realx+SEEX - g->u.posx, realy+SEEY - g->u.posy, c_magenta,'#'); + mvwputch(w, realy+SEEY + ext_y_offset - ycoord, realx+SEEX + ext_x_offset - xcoord, c_magenta,'#'); else - mvwputch(w, realx+SEEX - g->u.posx, realy+SEEY - g->u.posy, c_dkgray, '#'); + mvwputch(w, realy+SEEY + ext_y_offset - ycoord, realx+SEEX + ext_x_offset - xcoord, c_dkgray, '#'); } else if (dist <= g->u.clairvoyance() || sees(g->u.posx, g->u.posy, realx, realy, light, t)) - drawsq(w, g->u, realx, realy, false, true); + drawsq(w, g->u, realx, realy, false, true, vm, xshift, yshift); } } - mvwputch(w, SEEY, SEEX, g->u.color(), '@'); + mvwputch(w, g->u.posy + SEEY + ext_y_offset - ycoord, g->u.posx + SEEX + ext_x_offset - xcoord, g->u.color(), '@'); } void map::drawsq(WINDOW* w, player &u, int x, int y, bool invert, - bool show_items) + bool show_items, view_mode vm, int xshift, int yshift) { if (!INBOUNDS(x, y)) return; // Out of bounds - int k = x + SEEX - u.posx; - int j = y + SEEY - u.posy; + int ext_x_offset = 0; + int ext_y_offset = 0; + if(vm == EXTENDED){ + ext_x_offset = EXTX; + } + else if(vm == DEBUG){ + ext_x_offset = (COLS/2) - SEEX; + ext_y_offset = (LINES/2) - SEEY; + } + int k = x + SEEX + ext_x_offset - (u.posx + xshift); + int j = y + SEEY + ext_y_offset - (u.posy + yshift); nc_color tercol; long sym = terlist[ter(x, y)].sym; bool hi = false; diff --git a/map.h b/map.h index d166916ecd..237e6f9b5f 100644 --- a/map.h +++ b/map.h @@ -35,9 +35,10 @@ class map ~map(); // Visual Output - void draw(game *g, WINDOW* w); + void draw(game *g, WINDOW* w, view_mode vm = NORMAL, int xshift = 0, int yshift = 0); void debug(); - void drawsq(WINDOW* w, player &u, int x, int y, bool invert, bool show_items); + void drawsq(WINDOW* w, player &u, int x, int y, bool invert, bool show_items, + view_mode vm = NORMAL, int xshift = 0, int yshift = 0); // File I/O virtual void save(overmap *om, unsigned int turn, int x, int y); diff --git a/mapdata.h b/mapdata.h index 755105b635..5935527623 100644 --- a/mapdata.h +++ b/mapdata.h @@ -23,11 +23,17 @@ class monster; #endif // Nuts to 80x24 terms. Mostly exists in graphical clients, and // those fatcats can resize. +#ifndef EXTX //Extension of player vision in the X direction to reach 80 +#define EXTX 27 //columns. (2*(SEEX+27) + 1 = 79) +#endif + // mfb(t_flag) converts a flag to a bit for insertion into a bitfield #ifndef mfb #define mfb(n) long(1 << (n)) #endif + + enum t_flag { transparent = 0,// Player & monsters can see through/past it bashable, // Player & monsters can bash this & make it the next in the list diff --git a/monster.cpp b/monster.cpp index 80cfa33772..9d6a94da79 100644 --- a/monster.cpp +++ b/monster.cpp @@ -10,6 +10,8 @@ #if (defined _WIN32 || defined WINDOWS) #include "catacurse.h" + #define LINES 25 + #define COLS 80 #else #include #endif @@ -213,10 +215,19 @@ char monster::symbol() return type->sym; } -void monster::draw(WINDOW *w, int plx, int ply, bool inv) +void monster::draw(WINDOW *w, int plx, int ply, bool inv, view_mode vm, int xshift, int yshift) { - int x = SEEX + posx - plx; - int y = SEEY + posy - ply; + int ext_x_offset = 0; + int ext_y_offset = 0; + if(vm == EXTENDED){ + ext_x_offset = EXTX; + } + else if(vm == DEBUG){ + ext_x_offset = (COLS/2) - SEEX; + ext_y_offset = (LINES/2) - SEEY; + } + int x = SEEX + ext_x_offset + posx - (plx + xshift); + int y = SEEY + ext_y_offset + posy - (ply + yshift); nc_color color = type->color; if (friendly != 0 && !inv) mvwputch_hi(w, y, x, color, type->sym); diff --git a/monster.h b/monster.h index 62d7c4f8b5..3fc594e70b 100644 --- a/monster.h +++ b/monster.h @@ -58,7 +58,8 @@ class monster { std::string name_with_armor(); // Name, with whatever our armor is called void print_info(game *g, WINDOW* w); // Prints information to w. char symbol(); // Just our type's symbol; no context - void draw(WINDOW* w, int plx, int ply, bool inv); + void draw(WINDOW* w, int plx, int ply, bool inv, view_mode vm = NORMAL, + int xshift = 0, int yshift = 0); nc_color color_with_effects(); // Color with fire, beartrapped, etc. // Inverts color if inv==true bool has_flag(m_flag f); // Returns true if f is set (see mtype.h) diff --git a/npc.cpp b/npc.cpp index 2374194b93..bc660a3c26 100644 --- a/npc.cpp +++ b/npc.cpp @@ -10,6 +10,11 @@ #include "output.h" #include "line.h" +#if (defined _WIN32 || defined WINDOWS) + #define LINES 25 + #define COLS 80 +#endif + std::vector starting_clothes(npc_class type, bool male, game *g); std::vector starting_inv(npc *me, npc_class type, game *g); @@ -1929,10 +1934,19 @@ int npc::speed_estimate(int speed) return rng(low, high); } -void npc::draw(WINDOW* w, int ux, int uy, bool inv) +void npc::draw(WINDOW* w, int ux, int uy, bool inv, view_mode vm, int xshift, int yshift) { - int x = SEEX + posx - ux; - int y = SEEY + posy - uy; + int ext_x_offset = 0; + int ext_y_offset = 0; + if(vm == EXTENDED){ + ext_x_offset = EXTX; + } + else if(vm == DEBUG){ + ext_x_offset = (COLS/2) - SEEX; + ext_y_offset = (LINES/2) - SEEY; + } + int x = SEEX + ext_x_offset + posx - (ux + xshift); + int y = SEEY + ext_y_offset + posy - (uy + yshift); nc_color col = c_pink; if (attitude == NPCATT_KILL) col = c_red; diff --git a/npc.h b/npc.h index 5ecc484d6d..ba7f7bf956 100644 --- a/npc.h +++ b/npc.h @@ -407,7 +407,8 @@ class npc : public player { // Display - void draw(WINDOW* w, int plx, int ply, bool inv); + void draw(WINDOW* w, int plx, int ply, bool inv, view_mode vm = NORMAL, + int xshift = 0, int yshift = 0); void print_info(WINDOW* w); std::string short_description(); std::string opinion_text(); diff --git a/npctalk.cpp b/npctalk.cpp index b68357672a..9615dffed8 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -737,7 +737,7 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) int shift = p->chatbin.tempvalue; bool more = trainable.size() + styles.size() - shift > 9; for (int i = shift; i < trainable.size() && printed < 9; i++) { - shift--; + // shift--; printed++; std::stringstream skilltext; skill trained = trainable[i]; @@ -750,7 +750,7 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) } if (shift < 0) shift = 0; - for (int i = shift; i < styles.size() && printed < 9; i++) { + for (int i = 0; i < styles.size() && printed < 9; i++) { printed++; SELECT_TEMP( g->itypes[styles[i]]->name + " (cost 800)", 0 - styles[i] ); From da8635f0ec834099a9e66ddf9dddca0a256fdb63 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Fri, 6 Jul 2012 17:28:23 -0400 Subject: [PATCH 40/51] Fixed Windows makefile to work on my computer --- Makefile.Windows | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile.Windows b/Makefile.Windows index ef50d10d32..7eb993df05 100644 --- a/Makefile.Windows +++ b/Makefile.Windows @@ -2,7 +2,7 @@ # WARNINGS will spam hundreds of warnings, mostly safe, if turned on # DEBUG is best turned on if you plan to debug in gdb -- please do! # PROFILE is for use with gprof or a similar program -- don't bother generally -#WARNINGS = -Wall -Wextra -Wno-switch -Wno-sign-compare -Wno-missing-braces -Wno-unused-parameter -Wno-char-subscripts +#WARNINGS = -Wall #DEBUG = -g #PROFILE = -pg @@ -11,12 +11,13 @@ DDIR = .deps TARGET = cataclysm.exe -CXX = i486-mingw32-g++ +CXX = /usr/local/mingw/bin/i386-mingw32-g++ -LINKER = i486-mingw32-ld +LINKER = /usr/local/mingw/bin/i386-mingw32-ld LINKERFLAGS = -Wl,-stack,12000000,-subsystem,windows -CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) +CFLAGS = -O1 -std=gnu++0x +#CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) LDFLAGS = -static -lgdi32 From ccf6d064a6d7cbb03fe7fb980b19e625023d23a9 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Thu, 14 Jun 2012 11:30:47 -0400 Subject: [PATCH 41/51] Vehicles are no longer damaged by passing bullets --- map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map.cpp b/map.cpp index 0423be5d4c..74f147742b 100644 --- a/map.cpp +++ b/map.cpp @@ -1168,7 +1168,7 @@ void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) int vpart; vehicle &veh = veh_at (x, y, vpart); - if (veh.type != veh_null) + if (veh.type != veh_null && hit_items) { bool inc = (flags & mfb(IF_AMMO_INCENDIARY) || flags & mfb(IF_AMMO_FLAME)); dam = veh.damage (vpart, dam, inc? 2 : 0, hit_items); From 9bbbbef0b2a9d347764c6b2d87cac4caa874a113 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Wed, 6 Jun 2012 09:43:38 -0400 Subject: [PATCH 42/51] Make appropriate car parts generate in electronics store --- mapitemsdef.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mapitemsdef.cpp b/mapitemsdef.cpp index 961e8e4fb6..72c8ebf85a 100644 --- a/mapitemsdef.cpp +++ b/mapitemsdef.cpp @@ -183,7 +183,8 @@ void game::init_mapitems() itm_amplifier, itm_antenna, itm_battery, itm_soldering_iron, itm_screwdriver, itm_processor, itm_RAM, itm_mp3, itm_flashlight, itm_radio, itm_hotplate, itm_receiver, itm_transponder, itm_tazer, - itm_two_way_radio, itm_usb_drive, itm_manual_electronics, NULL); + itm_two_way_radio, itm_usb_drive, itm_manual_electronics, itm_motor, itm_motor_large, + itm_storage_battery, itm_solar_panel, NULL); setvector( mapitems[mi_sports], From 1fd8d22c5277f831286db9f215cd808e52b922ea Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Fri, 6 Jul 2012 18:12:07 -0400 Subject: [PATCH 43/51] Equipped styles show their inventory letter ':' --- inventory_ui.cpp | 4 +++- newcharacter.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/inventory_ui.cpp b/inventory_ui.cpp index 2c6518880d..e7e73a8b63 100644 --- a/inventory_ui.cpp +++ b/inventory_ui.cpp @@ -45,7 +45,9 @@ void print_inv_statics(game *g, WINDOW* w_inv, std::string title, else mvwprintz(w_inv, 3, 40, g->u.weapon.color_in_inventory(&(g->u)), "%c - %s", g->u.weapon.invlet, g->u.weapname().c_str()); - } else + } else if (g->u.weapon.is_style()) + mvwprintz(w_inv, 3, 40, c_ltgray, "%c - %s", g->u.weapon.invlet, g->u.weapname().c_str()); + else mvwprintz(w_inv, 3, 42, c_ltgray, g->u.weapname().c_str()); // Print worn items if (g->u.worn.size() > 0) diff --git a/newcharacter.cpp b/newcharacter.cpp index 3c5d531ab7..0ba677ab7a 100644 --- a/newcharacter.cpp +++ b/newcharacter.cpp @@ -220,7 +220,7 @@ End of cheatery */ } ret_null = item(g->itypes[0], 0); if (!styles.empty()) - weapon = item(g->itypes[ styles[0] ], 0); + weapon = item(g->itypes[ styles[0] ], 0, ':'); else weapon = item(g->itypes[0], 0); // Nice to start out less than naked. From 96aa4e3511b3f34f54a9fe3bec04dc0b847b4bc2 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Fri, 6 Jul 2012 18:12:34 -0400 Subject: [PATCH 44/51] Changes to Journey mode to get it to compile in Windows. --- gamemode.h | 1 + overmap.cpp | 4 ++-- west.cpp | 66 +++++++++++++++++++++++++++++++++++------------------ 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/gamemode.h b/gamemode.h index 26c9614e37..9a0aea7d00 100644 --- a/gamemode.h +++ b/gamemode.h @@ -186,6 +186,7 @@ struct west_game : public special_game { virtual void per_turn(game * g); int distance_to_horde(game *g); virtual void pre_action(game *g, action_id &act); + void spam_zombies (game * g, int n = 200, bool fast_only = false); }; diff --git a/overmap.cpp b/overmap.cpp index b8f15dad7a..8a56f86c79 100644 --- a/overmap.cpp +++ b/overmap.cpp @@ -2457,7 +2457,7 @@ void overmap::open(game *g, int x, int y, int z) fin.close(); pointers.push_back(new overmap(g, x, y+i, z)); } else - pointers.push_back(NULL); + pointers.push_back((overmap *) NULL); } // Fetch east and west for (int i = -1; i <= 1; i+=2) { @@ -2468,7 +2468,7 @@ void overmap::open(game *g, int x, int y, int z) fin.close(); pointers.push_back(new overmap(g, x+i, y, z)); } else - pointers.push_back(NULL); + pointers.push_back((overmap *) NULL); } // pointers looks like (north, south, west, east) generate(g, pointers[0], pointers[3], pointers[1], pointers[2]); diff --git a/west.cpp b/west.cpp index f1788df712..cb2ea06eb2 100644 --- a/west.cpp +++ b/west.cpp @@ -79,40 +79,62 @@ int west_game::distance_to_horde(game *g) { return g->global_location().x - horde_location; } +void west_game::spam_zombies (game * g, int n, bool fast_only) { + if (n <= 0) return; + g->cancel_activity(); + g->u.rem_disease(DI_SLEEP); + g->u.rem_disease(DI_LYING_DOWN); + int monx, mony; + for (int i = 0; i < n; i++) { + mon_id type = fast_only ? mon_zombie_fast : g->valid_monster_from(g->moncats[mcat_zombie]); + if (type != mon_null) { + monx = rng(0, SEEX * MAPSIZE - 1); + mony = rng(0, SEEY * MAPSIZE - 1); + monster zom = monster(g->mtypes[type]); + zom.spawn(monx, mony); + zom.wandx = g->u.posx; + zom.wandy = g->u.posy; + zom.moves = -100; + g->z.push_back(zom); + } + } +} + + void west_game::per_turn(game *g) { - auto spam_zombies = [&] (int n = 200, bool fast_only = false) { - if (n <= 0) return; - g->cancel_activity(); - g->u.rem_disease(DI_SLEEP); - g->u.rem_disease(DI_LYING_DOWN); - int monx, mony; - for (int i = 0; i < n; i++) { - mon_id type = fast_only ? mon_zombie_fast : g->valid_monster_from(g->moncats[mcat_zombie]); - if (type != mon_null) { - monx = rng(0, SEEX * MAPSIZE - 1); - mony = rng(0, SEEY * MAPSIZE - 1); - monster zom = monster(g->mtypes[type]); - zom.spawn(monx, mony); - zom.wandx = g->u.posx; - zom.wandy = g->u.posy; - zom.moves = -100; - g->z.push_back(zom); - } - } - }; + // auto spam_zombies = [&] (int n = 200, bool fast_only = false) { + // if (n <= 0) return; + // g->cancel_activity(); + // g->u.rem_disease(DI_SLEEP); + // g->u.rem_disease(DI_LYING_DOWN); + // int monx, mony; + // for (int i = 0; i < n; i++) { + // mon_id type = fast_only ? mon_zombie_fast : g->valid_monster_from(g->moncats[mcat_zombie]); + // if (type != mon_null) { + // monx = rng(0, SEEX * MAPSIZE - 1); + // mony = rng(0, SEEY * MAPSIZE - 1); + // monster zom = monster(g->mtypes[type]); + // zom.spawn(monx, mony); + // zom.wandx = g->u.posx; + // zom.wandy = g->u.posy; + // zom.moves = -100; + // g->z.push_back(zom); + // } + // } + // }; if (int(g->turn) % 300 == 0) { horde_location++; int dh = distance_to_horde(g); if (dh <= 0) { popup("The horde is upon you!!"); - spam_zombies(); + spam_zombies(g); } else if (dh <= 5) { popup("The horde is starting to catch up with you!\nThe main body of the horde is only %d map squares away!", dh); int badness = 5 - dh; - spam_zombies(dice(badness,4), true); + spam_zombies(g, dice(badness,4), true); } else if (dh < 15) { popup("The horde comes! They are only %d map squares away.", dh); From 9da250303d5060718858001d609142f6900d049e Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 8 Jul 2012 11:34:06 -0400 Subject: [PATCH 45/51] Improve NPC combat verbs --- melee.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/melee.cpp b/melee.cpp index 44e9715509..743b28bb36 100644 --- a/melee.cpp +++ b/melee.cpp @@ -1466,13 +1466,14 @@ std::vector player::mutation_attacks(monster *z, player *p) std::string melee_verb(technique_id tech, std::string your, player &p, int bash_dam, int cut_dam, int stab_dam) { + std::string s = (p.is_npc() ? "s" : ""); + if (tech != TEC_NULL && p.weapon.is_style() && p.weapon.style_data(tech).name != "") - return p.weapon.style_data(tech).name; + return p.weapon.style_data(tech).name + s; std::stringstream ret; - std::string s = (p.is_npc() ? "s" : ""); switch (tech) { From 0caedec68ffdbc5f98771e38cfc517cc6fd3f5ef Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 8 Jul 2012 11:34:23 -0400 Subject: [PATCH 46/51] Fix some NPC combat crashes --- ranged.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ranged.cpp b/ranged.cpp index 47edf1520c..5b2009b046 100644 --- a/ranged.cpp +++ b/ranged.cpp @@ -845,7 +845,8 @@ void shoot_player(game *g, player &p, player *h, int &dam, double goodhit) if (g->active_npc[npcdex].hp_cur[hp_head] <= 0 || g->active_npc[npcdex].hp_cur[hp_torso] <= 0 ) { g->active_npc[npcdex].die(g, !p.is_npc()); - g->active_npc.erase(g->active_npc.begin() + npcdex); + if(g->npc_at(h->posx, h->posy) != -1) + g->active_npc.erase(g->active_npc.begin() + npcdex); } } } From fa929c2df8ad7b012a1d8c62cbf8fadfb28c161f Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 8 Jul 2012 11:39:39 -0400 Subject: [PATCH 47/51] NPCs healing themselves now take movement points, NPCs won't try infinitely equipping styles --- npcmove.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/npcmove.cpp b/npcmove.cpp index 3840e15f1b..76bf6f3181 100644 --- a/npcmove.cpp +++ b/npcmove.cpp @@ -469,7 +469,7 @@ npc_action npc::method_of_attack(game *g, int target, int danger) enough_time_to_reload(g, target, inv[i])) { has_empty_gun = true; empty_guns.push_back(i); - } else if (inv[i].melee_value(sklevel) > weapon.melee_value(sklevel) * 1.1) + } else if (inv[i].melee_value(sklevel) > weapon.melee_value(sklevel) * 1.1 && !weapon.is_style()) has_better_melee = true; } @@ -1663,6 +1663,7 @@ void npc::heal_self(game *g) if (g->u_see(posx, posy, t)) g->add_msg("%s heals %sself.", name.c_str(), (male ? "him" : "her")); heal(worst, amount_healed); + moves -= 250; } void npc::use_painkiller(game *g) From f35469fcbb4ed1ad82675ed96915bfb027e6d873 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 8 Jul 2012 11:40:38 -0400 Subject: [PATCH 48/51] Some barter fixes, typo --- npctalk.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/npctalk.cpp b/npctalk.cpp index 9615dffed8..8c39853afc 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -293,7 +293,7 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) case ENGAGE_NONE: status << "not engaging enemies."; break; case ENGAGE_CLOSE: status << "engaging nearby enemies."; break; case ENGAGE_WEAK: status << "engaging weak enemies."; break; - case ENGAGE_HIT: status << "engaging enenmies you attack."; break; + case ENGAGE_HIT: status << "engaging enemies you attack."; break; case ENGAGE_ALL: status << "engaging all enemies."; break; } status << " " << (p->male ? "He" : "She") << " will " << @@ -1761,12 +1761,12 @@ Tab key to switch lists, letters to pick items, Enter to finalize, Esc to quit\n if (them_off + 17 < theirs.size()) mvwprintw(w_them, 19, 9, "More >"); // Draw your list of items, starting from you_off - for (int i = you_off; i < yours.size() && i < 17; i++) + for (int i = you_off; i < yours.size() && i < you_off + 17; i++) mvwprintz(w_you, i - you_off + 1, 1, (getting_yours[i] ? c_white : c_ltgray), "%c %c %s - $%d", char(i + 'a'), (getting_yours[i] ? '+' : '-'), - g->u.inv[yours[i + you_off]].tname().substr( 0,25).c_str(), - your_price[i + you_off]); + g->u.inv[yours[i]].tname().substr( 0,25).c_str(), + your_price[i]); if (you_off > 0) mvwprintw(w_you, 19, 1, "< Back"); if (you_off + 17 < yours.size()) From 7d66722de49ea3af3961e5a0b7347c9185e27ea4 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 8 Jul 2012 11:41:44 -0400 Subject: [PATCH 49/51] Mission lists and NPC combat engagement rules save. BREAKS SAVE COMPATIBILITY --- npc.cpp | 7 +++++++ npc.h | 14 ++++++++++++++ player.cpp | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/npc.cpp b/npc.cpp index bc660a3c26..c5dbe1f9e0 100644 --- a/npc.cpp +++ b/npc.cpp @@ -130,6 +130,8 @@ npc& npc::operator= (npc &rhs) for (int i = 0; i < rhs.styles.size(); i++) styles.push_back(rhs.styles[i]); + combat_rules = rhs.combat_rules; + marked_for_death = rhs.marked_for_death; dead = rhs.dead; @@ -198,6 +200,8 @@ npc& npc::operator= (const npc &rhs) for (int i = 0; i < rhs.styles.size(); i++) styles.push_back(rhs.styles[i]); + combat_rules = rhs.combat_rules; + marked_for_death = rhs.marked_for_death; dead = rhs.dead; @@ -255,6 +259,8 @@ std::string npc::save_info() dump << my_fac->id; dump << " " << attitude << " " << " " << op_of_u.save_info() << " " << chatbin.save_info(); + + dump << combat_rules.save_info(); // Inventory size, plus armor size, plus 1 for the weapon dump << std::endl << inv.num_items() + worn.size() + 1 << std::endl; @@ -360,6 +366,7 @@ void npc::load_info(game *g, std::string data) op_of_u.load_info(dump); chatbin.load_info(dump); + combat_rules.load_info(dump); } void npc::randomize(game *g, npc_class type) diff --git a/npc.h b/npc.h index ba7f7bf956..9c7bc4759f 100644 --- a/npc.h +++ b/npc.h @@ -262,6 +262,20 @@ struct npc_combat_rules use_guns = true; use_grenades = true; }; + + void load_info(std::istream &data) + { + int tmpen; + data >> tmpen >> use_guns >> use_grenades; + engagement = combat_engagement(tmpen); + } + + std::string save_info() + { + std::stringstream dump; + dump << " " << engagement << " " << use_guns << " " << use_grenades << " "; + return dump.str(); + } }; enum talk_topic { diff --git a/player.cpp b/player.cpp index 51625e43d8..e2f87a500d 100644 --- a/player.cpp +++ b/player.cpp @@ -137,6 +137,10 @@ player& player::operator= (player rhs) style_selected = rhs.style_selected; weapon = rhs.weapon; + active_missions = rhs.active_missions; + completed_missions = rhs.completed_missions; + failed_missions = rhs.failed_missions; + return (*this); } @@ -520,6 +524,25 @@ void player::load_info(game *g, std::string data) mortmp.item_type = g->itypes[item_id]; morale.push_back(mortmp); } + + int nummis = 0; + int mistmp; + dump >> nummis; + for (int i = 0; i < nummis; i++) { + dump >> mistmp; + active_missions.push_back(mistmp); + } + dump >> nummis; + for (int i = 0; i < nummis; i++) { + dump >> mistmp; + completed_missions.push_back(mistmp); + } + dump >> nummis; + for (int i = 0; i < nummis; i++) { + dump >> mistmp; + failed_missions.push_back(mistmp); + } + } std::string player::save_info() @@ -575,6 +598,19 @@ std::string player::save_info() dump << morale[i].item_type->id; dump << " "; } + + dump << " " << active_missions.size() << " "; + for (int i = 0; i < active_missions.size(); i++) + dump << active_missions[i] << " "; + + dump << " " << completed_missions.size() << " "; + for (int i = 0; i < completed_missions.size(); i++) + dump << completed_missions[i] << " "; + + dump << " " << failed_missions.size() << " "; + for (int i = 0; i < failed_missions.size(); i++) + dump << failed_missions[i] << " "; + dump << std::endl; for (int i = 0; i < inv.size(); i++) { From 7526f36f8401dd7e1b2b458dade2fcf0f5ef8434 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Sun, 8 Jul 2012 12:53:29 -0400 Subject: [PATCH 50/51] Improved spiral cavern generation --- mapgen.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/mapgen.cpp b/mapgen.cpp index 5714cb0cb2..198f088660 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -3695,15 +3695,49 @@ void map::draw_map(oter_id terrain_type, oter_id t_north, oter_id t_east, } int num_spiral = rng(1, 4); for (int i = 0; i < num_spiral; i++) { - int orx = rng(SEEX - 4, SEEX), ory = rng(SEEY - 4, SEEY); - line(this, t_rock, orx , ory , orx + 5, ory ); - line(this, t_rock, orx + 5, ory , orx + 5, ory + 5); - line(this, t_rock, orx + 1, ory + 5, orx + 5, ory + 5); - line(this, t_rock, orx + 1, ory + 2, orx + 1, ory + 4); - line(this, t_rock, orx + 1, ory + 2, orx + 3, ory + 2); - ter(orx + 3, ory + 3) = t_rock; - ter(orx + 2, ory + 3) = t_rock_floor; - place_items(mi_spiral, 60, orx + 2, ory + 3, orx + 2, ory + 3, false, 0); + int orx = rng(0, SEEX * 2 - 6), ory = rng(0, SEEY * 2 - 6); + switch (rng(0, 3)) { + case 0: + line(this, t_rock, orx , ory , orx + 5, ory ); + line(this, t_rock, orx + 5, ory , orx + 5, ory + 5); + line(this, t_rock, orx + 1, ory + 5, orx + 5, ory + 5); + line(this, t_rock, orx + 1, ory + 2, orx + 1, ory + 4); + line(this, t_rock, orx + 1, ory + 2, orx + 3, ory + 2); + ter(orx + 3, ory + 3) = t_rock; + ter(orx + 2, ory + 3) = t_rock_floor; + place_items(mi_spiral, 60, orx + 2, ory + 3, orx + 2, ory + 3, false, 0); + break; + case 1: + line(this, t_rock, orx + 5, ory , orx + 5, ory + 5); + line(this, t_rock, orx + 5, ory + 5, orx , ory + 5); + line(this, t_rock, orx , ory + 5, orx , ory + 1); + line(this, t_rock, orx , ory + 1, orx + 3, ory + 1); + line(this, t_rock, orx + 3, ory + 1, orx + 3, ory + 3); + ter(orx + 2, ory + 3) = t_rock; + ter(orx + 2, ory + 2) = t_rock_floor; + place_items(mi_spiral, 60, orx + 2, ory + 2, orx + 2, ory + 2, false, 0); + break; + case 2: + line(this, t_rock, orx + 5, ory + 5, orx , ory + 5); + line(this, t_rock, orx , ory + 5, orx , ory ); + line(this, t_rock, orx , ory , orx + 4, ory ); + line(this, t_rock, orx + 4, ory , orx + 4, ory + 3); + line(this, t_rock, orx + 4, ory + 3, orx + 2, ory + 3); + ter(orx + 2, ory + 2) = t_rock; + ter(orx + 3, ory + 2) = t_rock_floor; + place_items(mi_spiral, 60, orx + 3, ory + 2, orx + 3, ory + 2, false, 0); + break; + case 3: + line(this, t_rock, orx , ory + 5, orx , ory ); + line(this, t_rock, orx , ory , orx + 5, ory ); + line(this, t_rock, orx + 5, ory , orx + 5, ory + 4); + line(this, t_rock, orx + 5, ory + 4, orx + 2, ory + 4); + line(this, t_rock, orx + 2, ory + 4, orx + 2, ory + 2); + ter(orx + 3, ory + 2) = t_rock; + ter(orx + 3, ory + 3) = t_rock_floor; + place_items(mi_spiral, 60, orx + 3, ory + 3, orx + 3, ory + 3, false, 0); + break; + } } } break; From 5e7bba0d1a222d57fe07401fae9c1aee3d4ab817 Mon Sep 17 00:00:00 2001 From: "Creidieki M. Crouch" Date: Mon, 9 Jul 2012 11:24:58 -0400 Subject: [PATCH 51/51] Actually apply patch for bug #100 --- game.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/game.cpp b/game.cpp index dc9267b733..d3b05389a4 100644 --- a/game.cpp +++ b/game.cpp @@ -1629,14 +1629,18 @@ bool game::load_master() getline(fin, itemdata); if (item_place == 'I') tmpinv.push_back(item(itemdata, this)); - else if (item_place == 'C' && !tmpinv.empty()) + else if (item_place == 'C' && !tmpinv.empty()) { tmpinv[tmpinv.size() - 1].contents.push_back(item(itemdata, this)); + j--; + } else if (item_place == 'W') tmp.worn.push_back(item(itemdata, this)); else if (item_place == 'w') tmp.weapon = item(itemdata, this); - else if (item_place == 'c') + else if (item_place == 'c') { tmp.weapon.contents.push_back(item(itemdata, this)); + j--; + } } } active_npc.push_back(tmp);