diff --git a/Makefile b/Makefile index 7ee85c3844..2b3837c14d 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 = -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/Makefile.Windows b/Makefile.Windows index dc8e0ea1c7..7eb993df05 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 -std=gnu++0x +#CFLAGS = $(WARNINGS) $(DEBUG) $(PROFILE) LDFLAGS = -static -lgdi32 diff --git a/action.cpp b/action.cpp new file mode 100644 index 0000000000..3eb73a3b29 --- /dev/null +++ b/action.cpp @@ -0,0 +1,166 @@ +#include "game.h" +#include "keypress.h" +#include + +action_id look_up_action(std::string ident); + +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; + 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 == "look_surroundings") + return ACTION_LOOK_SURROUNDINGS; + if (ident == "inventory") + return ACTION_INVENTORY; + if (ident == "organize") + return ACTION_ORGANIZE; + 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 == "pick_style") + return ACTION_PICK_STYLE; + 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 == "missions") + return ACTION_MISSIONS; + 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_mode") + return ACTION_TOGGLE_DEBUGMON; + + return ACTION_NULL; +} diff --git a/action.h b/action.h new file mode 100644 index 0000000000..1c28900f66 --- /dev/null +++ b/action.h @@ -0,0 +1,70 @@ +#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, +ACTION_LOOK_SURROUNDINGS, +// Inventory Interaction (including quasi-inventories like bionics) +ACTION_INVENTORY, +ACTION_ORGANIZE, +ACTION_USE, +ACTION_WEAR, +ACTION_TAKE_OFF, +ACTION_EAT, +ACTION_READ, +ACTION_WIELD, +ACTION_PICK_STYLE, +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_MISSIONS, +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/bionics.cpp b/bionics.cpp index 0abe83d74c..2c22f49786 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; @@ -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 8aa1324eb3..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); @@ -377,16 +377,21 @@ int delwin(WINDOW *win) delete win; return 1; }; -inline void newline(WINDOW *win){ -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); }; @@ -482,14 +487,19 @@ 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 that this isnt a newline char + 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 + 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/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..174b99bbec 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,13 +97,7 @@ 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) Also ncurses options, e.g. A_BLINK support--not all terminals can display bright background colors, so black-on-dark-gray @@ -151,25 +128,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 +143,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 +155,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 +196,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 +226,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/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 8ed4fa502c..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() @@ -155,6 +166,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 @@ -192,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]; @@ -392,7 +408,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; @@ -418,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++) @@ -448,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)); @@ -467,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 || @@ -547,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/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 new file mode 100644 index 0000000000..8e3076715a --- /dev/null +++ b/defense.cpp @@ -0,0 +1,1313 @@ +#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 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 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; + } + +// 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(); +} diff --git a/dialogue.h b/dialogue.h index d22d339cce..449d8d6e9b 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,8 +100,12 @@ 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; + opinion_success = npc_opinion(); + opinion_failure = npc_opinion(); success = TALK_NONE; failure = TALK_NONE; } @@ -95,8 +116,12 @@ 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; + opinion_success = rhs.opinion_success; + opinion_failure = rhs.opinion_failure; success = rhs.success; failure = rhs.failure; } @@ -264,6 +289,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 +356,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 +377,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/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/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 e060a0d2fa..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); @@ -186,6 +192,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 +241,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 || @@ -622,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: @@ -664,6 +681,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 2b7db3e364..d3b05389a4 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 @@ -13,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(); @@ -35,11 +41,14 @@ 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. 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 +74,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 - in_tutorial = false; // We're not in a tutorial game + 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 @@ -89,6 +104,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 +183,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 +224,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 +231,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 +331,45 @@ 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) { + delete gamemode; + gamemode = get_special_game( special_game_id(sel2) ); + if (!gamemode->init(this)) { + delete gamemode; + gamemode = new special_game; + u = player(); + delwin(w_open); + return (opening_screen()); + } + start = true; + ch = 0; + } + } } } else if (layer == 3) { // Character Templates if (templates.size() == 0) @@ -421,60 +472,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); @@ -503,11 +500,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); } @@ -541,23 +535,15 @@ bool game::do_turn() death_screen(); return true; } +// Actual stuff + //build_monmap(); + 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!"); @@ -571,13 +557,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++; @@ -621,8 +610,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) @@ -637,6 +626,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); @@ -720,7 +710,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; @@ -742,15 +732,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; @@ -797,43 +778,90 @@ 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(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); + + bool doit = false;; + switch (u.activity.type) { + case ACT_NULL: + doit = false; + break; case ACT_READ: - if (query_yn("%s Stop reading?", message.c_str())) - u.activity.type = ACT_NULL; + if (query_yn("%s Stop reading?", s.c_str())) + doit = true; break; case ACT_RELOAD: - if (query_yn("%s Stop reloading?", message.c_str())) - u.activity.type = ACT_NULL; + if (query_yn("%s Stop reloading?", s.c_str())) + doit = true; break; case ACT_CRAFT: - if (query_yn("%s Stop crafting?", message.c_str())) - u.activity.type = ACT_NULL; + if (query_yn("%s Stop crafting?", s.c_str())) + doit = true; break; case ACT_BUTCHER: - if (query_yn("%s Stop butchering?", message.c_str())) - u.activity.type = ACT_NULL; + if (query_yn("%s Stop butchering?", s.c_str())) + doit = true; break; case ACT_BUILD: - if (query_yn("%s Stop construction?", message.c_str())) - u.activity.type = ACT_NULL; + case ACT_VEHICLE: + if (query_yn("%s Stop construction?", s.c_str())) + doit = true; break; default: - u.activity.type = ACT_NULL; + doit = true; } + + if (doit) + u.cancel_activity(); } void game::update_weather() @@ -854,6 +882,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]; @@ -867,12 +896,12 @@ 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 << "!"; - cancel_activity_query(weather_text.str()); + cancel_activity_query(weather_text.str().c_str()); } // Now update temperature @@ -901,8 +930,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) @@ -935,10 +968,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)]; @@ -1099,154 +1130,345 @@ 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()) { + if (ch != ' ' && ch != KEY_ESCAPE && ch != '\n') + add_msg("Unknown command: '%c'", ch); + return; + } + + action_id act = keymap[ch]; + + 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 - 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(this); + break; + + case ACTION_MOVE_N: + if (u.in_vehicle) + pldrive(0, -1); + else + plmove(0, -1); + break; + + case ACTION_MOVE_NE: + if (u.in_vehicle) + pldrive(1, -1); + else + plmove(1, -1); + break; + + case ACTION_MOVE_E: + if (u.in_vehicle) + pldrive(1, 0); + else + plmove(1, 0); + break; + + case ACTION_MOVE_SE: + 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: + if (u.in_vehicle) + pldrive(-1, 1); + else + plmove(-1, 1); + break; + + case ACTION_MOVE_W: + if (u.in_vehicle) + pldrive(-1, 0); + else + plmove(-1, 0); + break; + + case ACTION_MOVE_NW: + if (u.in_vehicle) + pldrive(-1, -1); + else + plmove(-1, -1); + break; + + case ACTION_MOVE_DOWN: + if (!u.in_vehicle) + vertical_move(-1, false); + break; + + case ACTION_MOVE_UP: + if (!u.in_vehicle) + vertical_move( 1, false); + break; + + case ACTION_OPEN: + open(); + break; + + case ACTION_CLOSE: + close(); + break; + + case ACTION_SMASH: + if (veh_ctrl) + handbrake(); + else + 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_LOOK_SURROUNDINGS: + draw_surroundings(EXTENDED); + 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_ORGANIZE: + reassign_item(); + 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_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; + + 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: + if (veh_ctrl) + { + veh.turret_mode++; + if (veh.turret_mode > 1) + veh.turret_mode = 0; + } + else + wait(); + break; + + case ACTION_CRAFT: + craft(); + break; + + case ACTION_CONSTRUCT: + if (u.in_vehicle) + add_msg("You can't construct while in vehicle."); + else + construction_menu(); + break; + + case ACTION_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; + } + 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_MISSIONS: + list_missions(); + 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: + debug2(); + 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 +1548,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()); @@ -1360,25 +1583,76 @@ 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; + 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; + 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; + 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)); + 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') { + tmp.weapon.contents.push_back(item(itemdata, this)); + j--; + } + } } + active_npc.push_back(tmp); + if (fin.peek() == '\n') + fin.get(junk); // Chomp that pesky endline } + fin.close(); return true; } -void game::load(std::string name) +bool game::load(std::string name) { std::ifstream fin; std::stringstream playerfile; @@ -1387,7 +1661,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; @@ -1441,14 +1715,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') @@ -1457,11 +1735,15 @@ 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(); set_adjacent_overmaps(true); draw(); + + return true; } void game::save() @@ -1493,11 +1775,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"); @@ -1520,6 +1816,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]; @@ -1574,8 +1880,14 @@ 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 + "Check NPC", // 13 + "Cancel", // 14 NULL); + int veh_num; + std::vector opts; switch (action) { case 1: wish(); @@ -1606,11 +1918,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; @@ -1622,12 +1942,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; @@ -1642,6 +1965,83 @@ 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"); + 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); + } + break; + + case 11: + for (int i = 0; i < num_skill_types; i++) + u.sklevel[i] += 3; + break; + + case 12: + for (int i = itm_style_karate; i <= itm_style_zui_quan; i++) + 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(); +} + +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(); @@ -1953,33 +2353,12 @@ 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 werase(w_terrain); - draw_ter(); - draw_footsteps(); + draw_ter(w_terrain); + draw_footsteps(w_terrain); mon_info(); // Draw Status draw_HP(); @@ -2026,59 +2405,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))) { - 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, realy + SEEY + ext_y_offset - u.posy, realx + SEEX + ext_x_offset - u.posx, + c_white, '?'); + else + 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(); - 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::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(); @@ -2491,7 +2984,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) @@ -2630,6 +3123,7 @@ void game::monmove() z.erase(z.begin() + i);// Delete us if no replacement found dead = true; } + //build_monmap(); } if (!dead) { @@ -2639,27 +3133,28 @@ 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 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... 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) { + 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); @@ -2678,6 +3173,7 @@ void game::monmove() levx, levy, 1, 1)); } z.erase(z.begin()+i); + //build_monmap(); i--; } else z[i].receive_moves(); @@ -2697,6 +3193,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()); @@ -2713,7 +3210,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 +3304,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 +3346,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; @@ -2900,14 +3398,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; } @@ -2937,12 +3451,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); @@ -3222,18 +3743,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)); } @@ -3249,21 +3790,12 @@ 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 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(); - } - } + 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) @@ -3277,70 +3809,70 @@ void game::explode_mon(int index) if (index < 0 || index >= z.size()) { 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 -// 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); + } + 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]; + } - 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); } } @@ -3360,12 +3892,31 @@ 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) + { + 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) { @@ -3383,8 +3934,6 @@ void game::open() u.moves += 100; } } - if (in_tutorial) - tutorial_message(LESSON_CLOSE); } void game::close() @@ -3395,12 +3944,19 @@ 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; + 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"); @@ -3412,8 +3968,6 @@ void game::close() add_msg("Invalid direction."); if (didit) u.moves -= 90; - if (in_tutorial) - tutorial_message(LESSON_SMASH); } void game::smash() @@ -3430,7 +3984,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)) { @@ -3471,21 +4025,224 @@ 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); +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; @@ -3493,7 +4250,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; @@ -3501,6 +4258,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; @@ -3523,8 +4298,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."); @@ -3758,7 +4531,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; @@ -3773,8 +4546,8 @@ 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(); - get_direction(mx, my, ch); + draw_ter(w_terrain); + get_direction(this, mx, my, ch); if (mx != -2 && my != -2) { // Directional key pressed lx += mx; ly += my; @@ -3791,6 +4564,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()); @@ -3806,6 +4581,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); @@ -3821,6 +4597,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()); @@ -3833,6 +4613,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."); @@ -3855,8 +4641,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?")) { @@ -3870,16 +4664,20 @@ 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; } - 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(); @@ -3889,105 +4687,56 @@ 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 + 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); 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--; + decrease_nextinv(); } 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()); - 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 || - 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()); - 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); + 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()); - 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; } @@ -3995,7 +4744,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; @@ -4127,102 +4876,51 @@ 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 + 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--; - 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--; + 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; - 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 || - 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--; - 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); + if (from_veh) + veh.remove_item (veh_part, curmit); + else + 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 +4931,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); @@ -4253,7 +4949,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, 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" : ""); + 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()); @@ -4362,14 +5082,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() @@ -4377,13 +5129,18 @@ 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; } 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; @@ -4406,14 +5163,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]); } @@ -4517,6 +5293,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) { @@ -4609,8 +5390,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() @@ -4646,7 +5425,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; } @@ -4743,20 +5522,11 @@ 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; } - 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() @@ -4766,17 +5536,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() @@ -4804,7 +5564,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); @@ -4818,7 +5578,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."); @@ -4926,12 +5686,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); @@ -4940,9 +5704,6 @@ void game::wield() if (success) u.recoil = 5; - - if (in_tutorial && u.weapon.is_gun()) - tutorial_message(LESSON_GUN_LOAD); } void game::read() @@ -4961,10 +5722,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); @@ -4988,6 +5752,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 @@ -4995,8 +5799,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? @@ -5017,72 +5826,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; } @@ -5118,10 +5867,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; @@ -5144,7 +5925,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()); @@ -5169,14 +5955,23 @@ 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! -// 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); +// 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); + 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()); @@ -5194,38 +5989,52 @@ 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); +// 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) { - 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); @@ -5238,6 +6047,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) && @@ -5245,22 +6061,18 @@ 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; 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); } } } @@ -5276,6 +6088,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) { @@ -5298,6 +6114,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. @@ -5377,7 +6320,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) { @@ -5415,6 +6358,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) && @@ -5461,7 +6405,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."); @@ -5480,7 +6424,7 @@ void game::vertical_move(int movez, bool force) } } - set_adjacent_overmaps(); + set_adjacent_overmaps(true); refresh_all(); } @@ -5529,7 +6473,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 || @@ -5574,36 +6518,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 @@ -5738,6 +6680,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++) @@ -5803,18 +6753,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 (!no_npc && one_in(100 + 15 * 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; @@ -5831,7 +6786,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) @@ -5954,13 +6909,14 @@ 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; } void game::gameover() { erase(); + gamemode->game_over(this); mvprintw(0, 35, "GAME OVER"); inv(); } @@ -6119,7 +7075,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 822507fc63..6090b8c44d 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,13 +20,18 @@ #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 #define BULLET_SPEED 10000000 #define EXPLOSION_SPEED 70000000 +#define PICKUP_RANGE 2 + enum tut_type { TUT_NULL, TUT_BASIC, TUT_COMBAT, @@ -64,10 +68,11 @@ 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 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, ...); void add_event(event_type type, int on_turn, int faction_id = -1, int x = -1, int y = -1); @@ -77,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) @@ -86,9 +93,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); @@ -104,10 +113,10 @@ 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); + 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 @@ -126,6 +135,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); @@ -141,6 +153,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(); @@ -158,11 +171,16 @@ 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 + 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 @@ -183,7 +201,8 @@ class game ter_id dragging; std::vector items_dragged; int weight_dragged; // Computed once, when you start dragging - bool debugmon; + bool debugmon; + bool no_npc; // Display data... TODO: Make this more portable? WINDOW *w_terrain; WINDOW *w_minimap; @@ -192,14 +211,14 @@ 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 - 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_tutorial(tut_type type); // Starts a new tutorial + 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 @@ -211,6 +230,9 @@ 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 void create_factions(); // Creates new factions (for a new game world) void create_starting_npcs(); // Creates NPCs that start near you @@ -220,6 +242,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' @@ -235,7 +258,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); @@ -268,10 +296,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 @@ -304,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 @@ -327,16 +358,13 @@ 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 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..a9e29e5077 --- /dev/null +++ b/gamemode.cpp @@ -0,0 +1,39 @@ +#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"; + case SGAME_WEST: return "Journey to the West"; + 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; + case SGAME_WEST: + ret = new west_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..9a0aea7d00 --- /dev/null +++ b/gamemode.h @@ -0,0 +1,194 @@ +#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, +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 +{ + virtual special_game_id id() { return SGAME_NULL; }; +// init is run when the game begins + 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 +// 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 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); + 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 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); + 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? + +}; + +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); + virtual void pre_action(game *g, action_id &act); + void spam_zombies (game * g, int n = 200, bool fast_only = false); + + +}; + +#endif diff --git a/help.cpp b/help.cpp index 200f431d07..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\ @@ -150,7 +151,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 +291,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(); @@ -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..e7e73a8b63 100644 --- a/inventory_ui.cpp +++ b/inventory_ui.cpp @@ -43,9 +43,11 @@ 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()); - } 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 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) @@ -110,7 +112,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 +120,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 +139,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 +167,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 +177,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..cfbf40121e 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,29 @@ 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_DISARM: return "Disarm"; + 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 fa6d8e3113..3e41dd0ece 100644 --- a/itype.h +++ b/itype.h @@ -54,15 +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_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, @@ -71,6 +78,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 +97,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 @@ -152,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, @@ -170,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 }; @@ -220,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 @@ -230,9 +248,53 @@ 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 }; +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") @@ -254,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 @@ -263,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; } @@ -275,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(); } @@ -292,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; @@ -315,6 +380,7 @@ struct itype melee_cut = pmelee_cut; m_to_hit = pm_to_hit; item_flags = pitem_flags; + techniques = ptechniques; } }; @@ -343,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, @@ -385,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, @@ -421,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, @@ -457,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, @@ -512,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, @@ -545,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, @@ -577,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, @@ -617,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, @@ -647,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, @@ -674,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, @@ -697,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, @@ -709,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; @@ -761,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) @@ -810,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 a6ba37cf26..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! @@ -106,25 +110,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 +141,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."); @@ -310,7 +314,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, @@ -462,21 +466,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, @@ -489,73 +493,73 @@ 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."); 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\ @@ -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,15 +709,16 @@ 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, // VOL WGT DAM CUT HIT FLAGS - 1, 3, 14, 0, -1, 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."); @@ -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, "\ @@ -764,17 +776,21 @@ 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, 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, 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."); +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, @@ -872,7 +898,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, "\ @@ -897,9 +923,10 @@ 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."); +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, "\ @@ -918,6 +946,168 @@ 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, 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."); + +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_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_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, +// 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."); +TECH( mfb(TEC_WBLOCK_1) ); + +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."); + +// 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)\ @@ -1014,6 +1204,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 @@ -1153,7 +1347,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), "\ @@ -1162,12 +1356,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. @@ -1251,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, @@ -1276,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)| @@ -1288,6 +1489,15 @@ 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."); +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), "\ The only hat for a gentleman. Look exquisite while laughing in the face\n\ @@ -1355,7 +1565,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,37 +1580,39 @@ 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", 7,100,AT_ARROW, c_green, WOOD, +// VOL WGT DMG AP RNG ACC REC COUNT + 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,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, 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,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, 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.", 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, 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); 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); @@ -1413,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.", @@ -1451,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); @@ -1466,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); @@ -1478,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.", @@ -1494,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.", @@ -1524,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.", @@ -1547,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." @@ -1555,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, 720,AT_223, c_dkgray, STEEL, - 2, 2, 36, 1, 24, 13, 30, 40, "\ +AMMO(".223 Remington", 8, 620,AT_223, c_dkgray, STEEL, + 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." @@ -1570,102 +1782,102 @@ 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, "\ + 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,1140,AT_223, c_dkgray, STEEL, - 2, 4, 28, 7, 25, 11, 32, 30, "\ +AMMO("5.56 incendiary", 2, 840,AT_223, c_dkgray, STEEL, + 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, 900,AT_3006, c_dkgray, STEEL, - 1, 7, 42, 2, 40, 12, 34, 20, "\ +AMMO(".270 Winchester", 8, 600,AT_3006, c_dkgray, STEEL, + 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,1050,AT_3006, c_dkgray, STEEL, - 1, 12, 50, 16, 40, 7, 36, 10, "\ +AMMO(".30-06 AP", 4, 650,AT_3006, c_dkgray, STEEL, + 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.", 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, "\ + 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, 920,AT_308, c_dkgray, STEEL, - 1, 9, 36, 1, 35, 7, 33, 20, "\ +AMMO(".308 Winchester", 7, 620,AT_308, c_dkgray, STEEL, + 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,1040,AT_308, c_dkgray, STEEL, - 1, 9, 44, 4, 35, 6, 34, 20, "\ +AMMO("7.62x51mm", 6, 680,AT_308, c_dkgray, STEEL, + 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.", 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, "\ + 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,1200,AT_FUSION, c_ltgreen, PLASTIC, - 1, 2, 12, 6, 20, 4, 0, 20, "\ +AMMO("fusion pack", 2, 800,AT_FUSION, c_ltgreen, PLASTIC, + 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 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,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\ @@ -1673,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,1200,AT_PLASMA, c_green, STEEL, - 10, 25, 35, 3, 8, 4, 0, 25, "\ +AMMO("hydrogen", 8, 800,AT_PLASMA, c_green, STEEL, + 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)); @@ -1689,12 +1901,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 +1936,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,68 +1944,68 @@ 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, "\ + 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.", 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 +2013,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 +2047,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 +2076,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 +2097,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 +2125,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 +2140,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 +2148,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 +2163,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 +2171,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 +2179,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,29 +2201,29 @@ ammunition, it is designed for burst fire.", 0); // NAME RAR PRC COLOR MAT1 MAT2 -GUN("Marlin 39A", 14, 800,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, "\ +GUN("Marlin 39A", 14,1600,c_brown,IRON, WOOD, +// 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, 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 +2231,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 +2260,22 @@ 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, +// 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.", 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 +2283,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 +2291,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 +2305,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 +2313,22 @@ 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, +// 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\ @@ -2122,7 +2337,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 +2345,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 +2353,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 +2381,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 +2396,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\ @@ -2390,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\ @@ -2429,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, "\ @@ -2656,7 +2871,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, @@ -2706,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, @@ -2714,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,"\ @@ -2925,14 +3142,14 @@ 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\ 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\ @@ -3011,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 @@ -3162,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 @@ -3177,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 16a08d3bf2..81435d0412 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; @@ -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]); @@ -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; @@ -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; @@ -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++) { @@ -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 b35efe93dc..54aa58a214 100644 --- a/keypress.cpp +++ b/keypress.cpp @@ -1,22 +1,15 @@ #include "keypress.h" +#include "action.h" +#include "game.h" long input() { long ch = getch(); switch (ch) { - 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; } @@ -66,3 +59,156 @@ void get_direction(int &x, int &y, char ch) y = -2; } } + +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; + 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: + case ACTION_PICKUP: + x = 0; + y = 0; + return; + default: + x = -2; + 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\ +look_surroundings 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\ +pick_style _\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\ +missions M\n\ +factions #\n\ +morale %\n\ +help ?\n\ +\n\ +# DEBUG FUNCTIONS\n\ +debug_mode ~\n\ +# debug Z\n\ +# debug_scent -\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/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 e061dc0068..74f147742b 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) @@ -55,6 +60,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 +643,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 +668,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 +715,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_ter_only(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 +742,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 +779,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; @@ -187,12 +800,58 @@ bool map::bash(int x, int y, int str, std::string &sound) i--; } } + + 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)) { + 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); + 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: + 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); + 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: + 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(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,10 +860,13 @@ 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: - 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; @@ -213,8 +875,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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); @@ -226,9 +891,12 @@ 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)) { + result = rng(0, 6); + if (res) *res = result; + if (str >= result) { sound += "glass breaking!"; ter(x, y) = t_window_frame; return true; @@ -237,8 +905,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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); @@ -250,8 +921,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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); @@ -263,8 +937,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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; @@ -273,8 +950,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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; @@ -283,9 +963,12 @@ 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)) { + result = dice(3, 45); + if (res) *res = result; + if (str >= result) { sound += "smash!"; ter(x, y) = t_floor; int num_boards = rng(4, 12); @@ -297,12 +980,15 @@ 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: 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; @@ -311,9 +997,12 @@ 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)) { + result = rng(60, 100); + if (res) *res = result; + if (str >= result) { sound += "glass breaking!"; ter(x, y) = t_floor; return true; @@ -322,8 +1011,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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); @@ -335,8 +1027,11 @@ 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)) { + result = rng(0, 30); + if (res) *res = result; + if (str >= result && !one_in(4)) { sound += "crunch."; ter(x, y) = t_dirt; return true; @@ -345,8 +1040,22 @@ 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)) { + result = rng(0, 40); + if (res) *res = result; + if (str >= result) { sound += "crunch!"; ter(x, y) = t_fungus; return true; @@ -355,8 +1064,11 @@ bool map::bash(int x, int y, int str, std::string &sound) return true; } 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; @@ -366,7 +1078,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); @@ -377,8 +1091,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; @@ -390,10 +1104,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)) @@ -402,8 +1117,10 @@ 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; + case t_door_c: case t_door_b: case t_door_locked: @@ -416,6 +1133,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,8 +1146,9 @@ void map::destroy(game *g, int x, int y, bool makesound) } ter(x, y) = t_rubble; 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; } @@ -439,14 +1158,35 @@ 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!"); 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 && hit_items) + { + 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: + 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 +1195,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) @@ -535,6 +1266,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)) @@ -863,9 +1598,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; @@ -897,9 +1630,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 @@ -958,6 +1689,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]; } @@ -993,13 +1729,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); } } @@ -1040,8 +1785,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; } @@ -1091,43 +1836,74 @@ 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; - 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 @@ -1170,6 +1946,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) @@ -1241,6 +2027,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, @@ -1305,6 +2092,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. @@ -1470,10 +2258,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) { @@ -1606,6 +2401,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; @@ -1634,6 +2434,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, @@ -1667,7 +2468,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; @@ -1703,6 +2504,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); @@ -1760,6 +2567,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) @@ -1807,6 +2623,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); @@ -1857,3 +2690,4 @@ void map::cast_to_nonant(int &x, int &y, int &n) } */ + diff --git a/map.h b/map.h index f05ac3e5e7..237e6f9b5f 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 @@ -34,18 +35,22 @@ 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); 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 + 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 @@ -57,12 +62,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(); @@ -70,7 +92,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); @@ -89,6 +112,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); @@ -120,8 +144,11 @@ 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; + 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); @@ -141,9 +168,9 @@ 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 *itypes; std::vector *traps; std::vector (*mapitems)[num_itloc]; diff --git a/mapdata.h b/mapdata.h index 502c13d1e2..5935527623 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. @@ -24,19 +23,27 @@ 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 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. + 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 @@ -47,6 +54,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 }; @@ -55,6 +63,7 @@ struct ter_t { char sym; nc_color color; unsigned char movecost; + trap_id trap; unsigned long flags;// : num_t_flags; }; @@ -72,7 +81,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 +98,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_log, t_root_wall, t_wax, t_floor_wax, t_fence_v, t_fence_h, @@ -110,6 +119,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, @@ -130,270 +140,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, - mfb(transparent)}, -{"pavement", '.', c_dkgray, 2, +{"metal floor", '.', c_ltcyan, 2, tr_null, mfb(transparent)}, -{"yellow pavement", '.', c_yellow, 2, +{"pavement", '.', c_dkgray, 2, tr_null, mfb(transparent)}, -{"sidewalk", '.', c_ltgray, 2, +{"yellow pavement", '.', c_yellow, 2, tr_null, mfb(transparent)}, -{"floor", '.', c_cyan, 2, +{"sidewalk", '.', c_ltgray, 2, tr_null, mfb(transparent)}, -{"metal grate", '#', c_dkgray, 2, +{"floor", '.', c_cyan, 2, tr_null, + mfb(transparent)|mfb(l_flammable)}, +{"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_brown, 4, +{"half-built wall", '#', c_ltred, 4, tr_null, mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)}, -{"wooden wall", '#', c_brown, 0, +{"wooden wall", '#', c_ltred, 0, tr_null, mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, -{"wall", '|', c_ltgray, 0, +{"chipped wood wall",'#', c_ltred, 0, tr_null, + mfb(bashable)|mfb(flammable)|mfb(noitem)|mfb(supports_roof)}, +{"broken wood wall", '&', c_ltred, 0, tr_null, + mfb(transparent)|mfb(bashable)|mfb(flammable)|mfb(noitem)| + mfb(supports_roof)}, +{"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, - mfb(transparent)|mfb(supports_roof)}, -{"closed wood door", '+', c_brown, 0, // Actually locked +{"open wood door", '\'', c_brown, 2, tr_null, + mfb(flammable)|mfb(transparent)|mfb(supports_roof)}, +{"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, - mfb(transparent)|mfb(supports_roof)}, -{"boarded up door", '#', c_brown, 0, +{"empty door frame", '.', c_brown, 2, tr_null, + mfb(flammable)|mfb(transparent)|mfb(supports_roof)}, +{"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, - mfb(noitem)}, -{"makeshift portcullis", '&', c_cyan, 0, +{"bulletin board", '6', c_blue, 0, tr_null, + mfb(flammable)|mfb(noitem)}, +{"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_green, 6, +{"underbrush", '#', c_ltgreen, 6, tr_null, mfb(transparent)|mfb(bashable)|mfb(diggable)|mfb(container)|mfb(rough)| mfb(flammable)}, -{"root wall", '#', c_brown, 0, +{"shrub", '#', c_green, 0, tr_null, + mfb(transparent)|mfb(bashable)|mfb(container)|mfb(flammable)}, +{"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, - mfb(transparent)}, -{"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, +{"wax floor", '.', c_yellow, 2, tr_null, + mfb(transparent)|mfb(l_flammable)}, +{"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, - mfb(transparent)|mfb(flammable)|mfb(diggable)}, -{"fungal tree", '7', c_dkgray, 0, +{"fungal bed", '#', c_dkgray, 3, tr_null, + mfb(transparent)|mfb(l_flammable)|mfb(diggable)}, +{"fungal tree", '7', c_dkgray, 0, tr_null, mfb(flammable)|mfb(noitem)}, -{"shallow water", '~', c_ltblue, 5, - mfb(transparent)|mfb(swimmable)}, -{"deep water", '~', c_blue, 0, +{"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)}, -{"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, - mfb(transparent)|mfb(bashable)}, -{"sandbox", '#', c_yellow, 3, +{"toilet", '&', c_white, 0, tr_null, + mfb(transparent)|mfb(bashable)|mfb(l_flammable)}, +{"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, - mfb(transparent)}, -{"radio tower", '&', c_ltgray, 0, +{"counter", '#', c_blue, 4, tr_null, + mfb(transparent)|mfb(flammable)}, +{"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)}, -{"refrigerator", '{', c_ltcyan, 0, +{"column", '1', c_ltgray, 0, tr_null, + mfb(flammable)}, +{"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, - mfb(transparent)|mfb(container)}, -{"book case", '{', c_brown, 0, +{"display rack", '{', c_ltgray, 0, tr_null, + mfb(transparent)|mfb(container)|mfb(l_flammable)}, +{"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)} }; @@ -599,6 +620,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 4b558974a6..198f088660 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); @@ -395,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; @@ -1071,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); @@ -1171,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; @@ -1182,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); @@ -2436,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; @@ -2573,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; } @@ -2913,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) ); } } @@ -3672,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; @@ -4131,8 +4188,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 +4226,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 +4615,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 +4940,231 @@ 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, 4)) { + + 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); +// 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); + 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); + x = rng(5, 10); + y = rng(13, 18); + line(this, t_wall_h, 1, mw, cw, mw); + 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(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 + 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; + 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(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; + 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++) { @@ -6115,6 +6400,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? @@ -6142,6 +6453,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: @@ -6173,6 +6485,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: @@ -6204,6 +6522,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: @@ -6234,12 +6559,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]; @@ -6640,9 +6977,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 { @@ -6672,9 +7009,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); } @@ -7088,6 +7425,296 @@ 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 || shortest <= 4) + 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 <= 1), walled_north = (y1 == 0), + walled_east = (x2 == SEEX * 2 - 1), walled_south = (y2 >= SEEY * 2 - 2); + + 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; + } + if (walled_south && x1 <= SEEX && SEEX <= x2) { + m->ter(SEEX - 1, y2) = grass_or_dirt(); + m->ter(SEEX, y2) = grass_or_dirt(); + } + } + 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; @@ -7103,7 +7730,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/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..72c8ebf85a 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], @@ -93,6 +94,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, @@ -152,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], @@ -163,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], @@ -177,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, 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], @@ -213,6 +220,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, @@ -374,7 +385,12 @@ 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], + 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], @@ -382,7 +398,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, @@ -393,6 +409,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( @@ -452,10 +469,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], @@ -641,14 +658,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 @@ -725,7 +743,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 116640de9e..743b28bb36 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_cutting], 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()); - z->add_item(remove_weapon()); - if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) - z->speed *= .7; - else - z->speed *= .85; - } 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,128 @@ 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) +{ + 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 + s; + + std::stringstream ret; + + + 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 b0559177f2..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 }; @@ -60,8 +63,10 @@ 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 + 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 44c501f0aa..102bf126a0 100644 --- a/monattack.cpp +++ b/monattack.cpp @@ -1127,19 +1127,19 @@ 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; } } + 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()); @@ -1164,17 +1164,18 @@ 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 - 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, 3); + 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()); @@ -1218,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 @@ -1255,7 +1260,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 +1274,54 @@ 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++; +} + +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 1de778b06d..beea39e7f9 100644 --- a/monattack.h +++ b/monattack.h @@ -41,6 +41,8 @@ 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); + void upgrade (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..30ee15ba63 100644 --- a/mongroup.h +++ b/mongroup.h @@ -20,9 +20,14 @@ 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 }; +bool moncat_is_safe(moncat_id id); + struct mongroup { moncat_id type; int posx, posy; @@ -38,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 ea8fab44b3..5ed86cfa00 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); @@ -21,11 +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, mon_creeper_hub, - mon_creeper_vine, mon_biollante, mon_triffid_heart, 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, @@ -55,5 +54,22 @@ 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); } +bool moncat_is_safe(moncat_id id) +{ + if (id == mcat_null || id == mcat_forest) + return true; + return false; +} 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/monmove.cpp b/monmove.cpp index f532eada15..382c5de6d4 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--; @@ -209,6 +213,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) @@ -457,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; @@ -468,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."); @@ -500,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!"); @@ -510,13 +526,30 @@ 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); 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(); } } @@ -621,6 +654,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 a31f2a5539..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 @@ -84,8 +86,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 +119,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 +150,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) { @@ -176,7 +189,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"; @@ -202,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); @@ -220,7 +242,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); @@ -260,7 +282,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 +297,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 +345,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 +384,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 +416,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 +562,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; } @@ -552,10 +582,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; } @@ -575,6 +607,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 @@ -684,6 +731,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)); } @@ -731,7 +784,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/monster.h b/monster.h index ee092ed622..3fc594e70b 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 @@ -57,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) @@ -88,9 +90,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 +109,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 0eeef6a612..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, @@ -75,6 +76,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 +101,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 }; @@ -122,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 @@ -167,8 +172,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..105faa1fae 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." @@ -325,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 @@ -348,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 @@ -410,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, @@ -571,7 +607,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\ @@ -707,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\ @@ -839,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 @@ -849,18 +885,18 @@ 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 - 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 @@ -888,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\ @@ -909,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\ @@ -920,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, 8, + 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\ @@ -931,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\ @@ -941,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\ @@ -952,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\ @@ -962,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\ @@ -973,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\ @@ -985,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\ @@ -1037,11 +1073,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.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/mutation_data.cpp b/mutation_data.cpp index 372fa405e3..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); @@ -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..0ba677ab7a 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; } @@ -255,8 +279,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) { @@ -529,8 +551,8 @@ 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); + 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/npc.cpp b/npc.cpp index c015a4131f..c5dbe1f9e0 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); @@ -43,9 +48,13 @@ 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; + for (int i = 0; i < num_skill_types; i++) + sklevel[i] = 0; } npc::npc(const npc &rhs) @@ -91,8 +100,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 +123,17 @@ 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]); + + combat_rules = rhs.combat_rules; + marked_for_death = rhs.marked_for_death; + dead = rhs.dead; return *this; } @@ -149,8 +171,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 +193,17 @@ 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]); + + combat_rules = rhs.combat_rules; + marked_for_death = rhs.marked_for_death; + dead = rhs.dead; return *this; } @@ -177,13 +211,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 +228,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 +252,64 @@ 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(); + + 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; 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 +320,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 +352,21 @@ 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); + combat_rules.load_info(dump); } void npc::randomize(game *g, npc_class type) @@ -312,12 +376,12 @@ 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); - 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; @@ -337,13 +401,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 +423,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 +440,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 +449,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 +489,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 +1086,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 +1138,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 +1233,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; + } + 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()); + return true; + } + + if (index >= inv.size()) { debugmsg("npc::wield(%d) [inv.size() = %d]", index, inv.size()); return false; } @@ -1203,10 +1303,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) @@ -1244,7 +1344,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)) @@ -1273,15 +1373,35 @@ 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 < 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 < -8) + else if (op_of_u.fear - 2 * personality.aggression - personality.bravery < -30) attitude = NPCATT_KILL; else 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; @@ -1364,6 +1484,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); @@ -1550,7 +1700,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; } } } @@ -1614,7 +1764,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 +1784,11 @@ bool npc::is_following() } } +bool npc::is_leader() +{ + return (attitude == NPCATT_LEAD); +} + bool npc::is_enemy() { if (attitude == NPCATT_KILL || attitude == NPCATT_MUG || @@ -1785,10 +1941,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; @@ -1935,6 +2100,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 +2113,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 +2123,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) @@ -2001,3 +2170,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 2d0f3d07c2..9c7bc4759f 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 @@ -48,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 @@ -59,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. @@ -72,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 }; @@ -103,6 +113,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 +152,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 +183,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 +198,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 { @@ -178,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 { @@ -206,18 +304,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 +348,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,12 +416,13 @@ 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(); // 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(); @@ -284,19 +434,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 +551,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 +586,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..76bf6f3181 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; } } @@ -467,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; } @@ -546,13 +548,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) { @@ -646,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; @@ -685,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; @@ -718,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 @@ -753,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; } +*/ } } } @@ -814,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()); } @@ -828,6 +858,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 +870,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 +891,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 +1355,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 +1369,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 +1466,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 +1518,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 @@ -1614,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) @@ -1738,6 +1788,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), @@ -1819,6 +1870,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 +1927,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..8c39853afc 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("%s is fleeing you!", name.c_str()); + return; + } else if (attitude == NPCATT_KILL) { + g->add_msg("%s 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; @@ -78,14 +97,11 @@ 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()); - return; - } + if (d.topic_stack.back() == TALK_NONE) + d.topic_stack.back() = pick_talk_topic(&(g->u)); moves -= 100; decide_needs(); - d.win = newwin(25, 80, 0, 0); wborder(d.win, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, @@ -192,6 +208,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 +221,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 +255,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?"; @@ -226,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 " << @@ -240,6 +307,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 +438,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 +587,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 +653,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 +717,86 @@ 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 = 0; 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); + } + 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; + + 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 +857,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 +986,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 +1196,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 +1237,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 +1251,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 +1328,72 @@ 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) +{ + 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; +} + +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); + 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; +} void talk_function::toggle_use_guns(game *g, npc *p) { @@ -932,6 +1430,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 >= 0 - 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 +1544,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 +1602,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 +1631,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 +1715,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 { @@ -1237,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()) @@ -1372,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/omdata.h b/omdata.h index a40c4ab824..0141983389 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}, @@ -395,6 +399,8 @@ enum omspec_id OMSPEC_OUTPOST, OMSPEC_SILO, OMSPEC_RADIO, + OMSPEC_MANSION, + OMSPEC_MANSION_WILD, OMSPEC_MEGASTORE, OMSPEC_HOSPITAL, OMSPEC_SEWAGE, @@ -419,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, @@ -438,25 +444,31 @@ 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_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)}, {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 +481,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 +500,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..da04206e33 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" @@ -224,7 +222,7 @@ void draw_tabs(WINDOW *w, int active_tab, ...) va_list ap; va_start(ap, active_tab); char *tmp; - while (tmp = (char *)va_arg(ap, int)) + while (tmp = va_arg(ap, char *)) labels.push_back((std::string)(tmp)); va_end(ap); @@ -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, ...) @@ -637,6 +652,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; @@ -686,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 580374f82e..a9c8c425e0 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); @@ -49,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 eba3b7ded9..8a56f86c79 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"); @@ -990,7 +1008,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; @@ -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) @@ -1795,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 @@ -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') { @@ -2436,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) { @@ -2447,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/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 8fb6a50a9d..e2f87a500d 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) @@ -18,6 +19,7 @@ #endif nc_color encumb_color(int level); +bool activity_is_suspendable(activity_type type); player::player() { @@ -30,7 +32,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,14 +45,19 @@ 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++) { sklevel[i] = 0; skexercise[i] = 0; @@ -64,6 +72,11 @@ player::player() mutation_category_level[i] = 0; } +player::player(const player &rhs) +{ + *this = rhs; +} + player::~player() { } @@ -79,7 +92,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; @@ -91,6 +105,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; @@ -114,6 +130,17 @@ 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; + + active_missions = rhs.active_missions; + completed_missions = rhs.completed_missions; + failed_missions = rhs.failed_missions; + return (*this); } @@ -121,6 +148,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)) @@ -139,7 +167,10 @@ 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 if (has_active_bionic(bio_hydraulics)) str_cur += 20; @@ -159,7 +190,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)) @@ -282,8 +313,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; @@ -303,17 +334,14 @@ 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) { - 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)) @@ -404,6 +432,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) @@ -417,12 +447,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; + 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]; @@ -438,8 +476,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++) { @@ -480,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() @@ -490,9 +553,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 << " "; + " " << 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] << " "; @@ -505,6 +571,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 << " "; @@ -528,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++) { @@ -808,7 +891,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++; @@ -881,7 +965,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); @@ -1343,13 +1427,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"); } @@ -1405,36 +1490,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) @@ -1651,6 +1800,7 @@ int player::clairvoyance() { if (has_artifact_with(AEP_CLAIRVOYANCE)) return 3; + return 0; } bool player::has_two_arms() @@ -1675,7 +1825,7 @@ bool player::avoid_trap(trap* tr) return false; } -void player::pause() +void player::pause(game *g) { moves = 0; if (recoil > 0) { @@ -1686,6 +1836,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) @@ -1704,9 +1865,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; } @@ -1838,6 +1999,14 @@ void player::hit(game *g, body_part bphurt, int side, int dam, int cut) if (dam <= 0) return; + 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); std::vector valid; @@ -1944,6 +2113,9 @@ void player::hurt(game *g, body_part bphurt, int side, int dam) if (dam <= 0) return; + if (!is_npc()) + g->cancel_activity_query("You were hurt!"); + if (has_trait(PF_PAINRESIST)) painadd = dam / 3; else @@ -2070,6 +2242,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; @@ -2114,13 +2385,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++; @@ -2128,7 +2403,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; @@ -2160,6 +2435,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) @@ -2407,7 +2691,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!"); } } @@ -2498,11 +2783,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 - radiation += rng(0, g->m.radiation(posx, posy) / 4); - if (rng(1, 1000) < radiation && int(g->turn) % 600 == 0) { + if (is_wearing(itm_hazmat_suit)) { + 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, 2500) < radiation && (int(g->turn) % 150 == 0 || radiation > 2000)){ mutate(g); if (radiation > 2000) radiation = 2000; @@ -2551,8 +2838,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) { @@ -2686,6 +2973,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 @@ -2712,6 +3004,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]); } @@ -2720,6 +3013,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; @@ -2830,6 +3124,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; } @@ -3311,16 +3612,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; @@ -3363,7 +3671,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 @@ -3463,27 +3771,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!"); @@ -3505,6 +3827,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 +3840,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 +3853,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; } @@ -3536,6 +3861,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; @@ -3629,6 +3967,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 +4022,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 +4112,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 +4141,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(), @@ -3845,6 +4186,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; @@ -3924,6 +4270,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); } @@ -3934,7 +4286,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; @@ -4026,6 +4383,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; } @@ -4064,6 +4422,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; } @@ -4220,6 +4579,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++) { @@ -4243,6 +4604,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; @@ -4278,7 +4656,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(); } @@ -4295,3 +4766,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 1e7b538a5d..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; @@ -246,7 +286,10 @@ class player { bool inv_sorted; //std::vector inv; 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 983a9ba6ea..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 @@ -260,7 +308,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,// @@ -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, " -------------------------------------------------- "}, @@ -607,8 +658,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."}, @@ -765,14 +816,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 196b6185d1..5b2009b046 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) ); } } @@ -104,6 +112,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); @@ -151,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 @@ -244,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]); @@ -349,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)); @@ -405,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; @@ -420,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(), @@ -428,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); @@ -472,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, " "); @@ -484,7 +508,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); @@ -679,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 @@ -820,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); } } } diff --git a/setvector.cpp b/setvector.cpp index a7082a7627..83f019f032 100644 --- a/setvector.cpp +++ b/setvector.cpp @@ -62,7 +62,7 @@ void setvector(std::vector &vec, ... ) va_list ap; va_start(ap, vec); char *tmp; - while (tmp = (char *)va_arg(ap, int)) + while (tmp = va_arg(ap, char *)) vec.push_back((std::string)(tmp)); va_end(ap); } @@ -108,17 +108,28 @@ 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); +} + +void setvector(std::vector &vec, ... ) +{ + va_list ap; + va_start(ap, vec); + char *tmpname; + technique_id tmptech; + int tmplevel; + + while (tmpname = va_arg(ap, char *)) { + 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 f288860d0a..333fb98873 100644 --- a/setvector.h +++ b/setvector.h @@ -15,6 +15,7 @@ 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, ... ); +void setvector(std::vector &vec, ... ); template void setvec(std::vector &vec, ... ); -//void setvector(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/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/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 22a645ac6a..042a537df9 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); @@ -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/tutorial.cpp b/tutorial.cpp new file mode 100644 index 0000000000..40d88e0480 --- /dev/null +++ b/tutorial.cpp @@ -0,0 +1,224 @@ +#include "game.h" +#include "output.h" +#include "action.h" +#include "tutorial.h" + +bool 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; + + return true; +} + +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\ diff --git a/veh_interact.cpp b/veh_interact.cpp new file mode 100644 index 0000000000..a2d1ff0ae3 --- /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 : 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); + 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); + 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); + 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); + 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..a977626933 --- /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, 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) }, + { "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, 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, 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, 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, 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, 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, 2, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "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, 4, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "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, 3, + mfb(vpf_internal) | mfb(vpf_engine) }, + { "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, 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, 0, + mfb(vpf_over) | mfb(vpf_cargo) }, + { "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, 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, 3, + mfb(vpf_over) | mfb(vpf_solar_panel) }, + + { "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, 5, + mfb(vpf_over) | mfb(vpf_turret) }, + { "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, 2, + mfb(vpf_internal) | mfb(vpf_armor) }, + { "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, 3, + 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..474a6f0e75 --- /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.move_cost_ter_only(x, y) == 0 && 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 diff --git a/west.cpp b/west.cpp new file mode 100644 index 0000000000..cb2ea06eb2 --- /dev/null +++ b/west.cpp @@ -0,0 +1,165 @@ +#include "gamemode.h" +#include "game.h" +#include "setvector.h" +#include "keypress.h" +#include "itype.h" +#include "mtype.h" +#include +#include +#include + +bool west_game::init (game *g) +{ + if(!g->u.create(g, PLTYPE_CUSTOM)) + return false; + + if (g->load(g->u.name)) { + 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)); + popup_top("The horde comes."); + + return true; + } + + // g->start_game(); + 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 - 25; + + return true; +} + +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); + // } + // } + // }; + + 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(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(g, dice(badness,4), true); + } + 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."); + } + } +} + +void west_game::pre_action(game *g, action_id &act) +{ + if (act == ACTION_SAVE) { + g->u.scent = 500 + horde_location; + } +} diff --git a/wish.cpp b/wish.cpp index 865020ba5f..2b5fbba288 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; @@ -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())