HEAD: sfllaw/jim's patch for xplc detection broke xplc detection in
[wvapps.git] / twc / src / twcship.cc
blob769117d6272e1ec753cbfcfcd64baf2be7c23095
1 #include "twcgamecontroller.h"
2 #include "twcplayerclient.h"
3 #include "twcutils.h"
4 #include <wvstrutils.h>
6 TWCShip::TWCShip(UniConf _cfg, TWCGameController *_game)
7 : TWCObject(_cfg, _game), warping_to(NULL)
9 set_captain(NULL);
12 TWCSector *TWCShip::sector()
14 return game->get_sector(TWCSectorCoord(cfg["location"].getme()));
18 TWCPlayerClient *TWCShip::captain()
20 return game->curplayers[cfg["captain"].getmeint()];
24 TWCPlayer *TWCShip::owner()
26 return game->players[cfg["owner"].getmeint()];
30 TWCShieldClass *TWCShip::shieldclass()
32 // Eventually we'll support more than one shield.
33 int shield = 0;
34 UniConf::Iter i(cfg["shields"]);
35 for (i.rewind(); i.next(); )
36 if (i().getmeint())
38 shield = i().key().printable().num();
39 break;
42 return game->shieldclasses[shield];
46 TWCShipClass *TWCShip::shipclass()
48 return game->shipclasses[cfg["class"].getmeint()];
52 TWCWeaponClass *TWCShip::weaponclass()
54 // Eventually we'll support more than one weapon.
55 int weapon = 0;
56 UniConf::Iter i(cfg["weapons"]);
57 for (i.rewind(); i.next(); )
58 if (i().getmeint())
60 weapon = i().key().printable().num();
61 break;
64 return game->weaponclasses[weapon];
68 bool TWCShip::weapon_charged()
70 return cfg["weapon charged"].getmeint();
74 int TWCShip::free_holds()
76 int res = total_holds();
78 UniConf::Iter i(cfg["commodities"]);
79 for (i.rewind(); i.next(); )
81 if (isint(i().key().printable()))
82 res -= i().getmeint();
85 return res;
89 bool TWCShip::install_weapon(TWCWeaponClass *weapon)
91 if (!weapon || weaponclass())
92 return false;
94 cfg["weapons"][weapon->id].setmeint(1);
95 return true;
99 bool TWCShip::uninstall_weapon(TWCWeaponClass *weapon)
101 if (!weaponclass())
102 return false;
104 cfg["weapons"][weapon->id].remove();
105 return true;
109 bool TWCShip::install_shield(TWCShieldClass *shield)
111 if (!shield || shieldclass())
112 return false;
114 cfg["shields"][shield->id].setmeint(1);
115 cfg["shield strength"].setmeint(shield->strength());
116 return true;
120 bool TWCShip::uninstall_shield(TWCShieldClass *shield)
122 if (!shieldclass())
123 return false;
125 cfg["shields"][shield->id].remove();
126 return true;
130 bool TWCShip::load(int type, int num)
132 game->log("Loading %s of type %s.\n", num, type);
133 if (free_holds() < num)
134 return false;
136 incr_uniconf(cfg["commodities"][type], num);
137 return true;
141 bool TWCShip::unload(int type, int num)
143 if (cfg["commodities"][type].getmeint() < num)
144 return false;
146 decr_uniconf(cfg["commodities"][type], num);
147 return true;
151 void TWCShip::inventory(TWCShipInventoryList &inv)
153 TWCShipInventory *item;
154 UniConf::Iter i(cfg["commodities"]);
155 for (i.rewind(); i.next(); )
157 if (!i().getmeint())
158 continue;
159 item = new TWCShipInventory;
160 item->commodity = game->commodities[i().key().printable().num()];
161 item->number = i().getmeint();
162 inv.append(item, true);
167 bool TWCShip::beam_cargo_aboard(TWCShip *from_ship, int type, int num)
169 if (from_ship->sector() != sector())
170 return false;
171 if (!from_ship->unload(type, num))
172 return false;
173 if (!load(type, num))
175 from_ship->load(type, num);
176 return false;
178 return true;
182 TWCShip *TWCShip::towing()
184 return game->ships[cfg["towing"].getmeint()];
188 TWCShip *TWCShip::towed_by()
190 return game->ships[cfg["towed by"].getmeint()];
194 bool TWCShip::tow(TWCShip *target)
196 if (!target)
198 if (!towing())
199 return false;
201 TWCShip *oldtowing = towing();
202 cfg["towing"].setmeint(0);
203 oldtowing->tow_attempt(NULL);
204 sector()->announce(TractorBeamDropped, id, oldtowing->id,
205 WvString("%s deactivates the tractor beam on %s.\n",
206 name(), oldtowing->name()), id);
207 return true;
210 if (target->tow_attempt(this))
212 cfg["towing"].setmeint(target->id);
213 sector()->announce(TractorBeamEngaged, id, target->id,
214 WvString("%s locks %s in a tractor beam.\n",
215 name(), target->name()), id);
216 return true;
218 return false;
222 bool TWCShip::tow_attempt(TWCShip *by)
224 if (!by)
226 cfg["being towed"].setmeint(0);
227 return true;
230 if (captain())
231 return false;
233 cfg["being towed"].setmeint(by->id);
234 cfg["towing"].setmeint(0); // just to be sure
235 return true;
239 TWCObjectIf *TWCShip::create_if(TWCHumanPlayerClient &plyrcli)
241 return new TWCShipIf(*this, plyrcli);
245 void TWCShip::set_captain(TWCPlayerClient *plyrcli)
247 if (plyrcli)
248 cfg["captain"].setmeint(plyrcli->player.id);
249 else
250 cfg["captain"].setme("");
254 void TWCShip::raise_shields()
256 cfg["shields up"].setmeint(1);
257 sector()->announce(ShieldsRaised, id, 0,
258 WvString("%s raises its shields.\n", name()), id);
262 void TWCShip::lower_shields()
264 cfg["shields up"].setmeint(0);
265 sector()->announce(ShieldsDropped, id, 0,
266 WvString("%s lowers its shields.\n", name()), id);
270 TWCDockResults TWCShip::dock()
272 TWCPort *port = sector()->port();
273 if (port)
275 TWCDockResults res = port->docking(this);
276 if (res == DockSuccess)
277 game->curplayers[captain()->player.id]->set_location(port);
279 return res;
281 return NoPort;
285 void TWCShip::charge_weapon(int)
287 if (!weapon_charged())
289 cfg["weapon charged"].setmeint(1);
290 if (captain())
291 captain()->msg("Weapon charged.\n");
296 WvString TWCShip::viable_target(WvStringParm shipnum)
298 TWCShip *ship = game->ships[shipnum.num()];
299 if (!ship)
300 return WvString("No such ship!\n");
302 if (ship == this)
303 return WvString("You can't target your own ship!\n");
305 if (ship->sector() != sector())
306 return WvString("This ship isn't here!\n");
308 return WvString::null;
312 TWCAttackResults TWCShip::shoot(TWCShip *target, int power)
314 if (!target)
315 return Aborted;
317 if (!weapon_charged())
318 return Aborted;
320 // automatically raise shields
321 if (!target->are_shields_up())
322 target->raise_shields();
324 game->log("%s is attacking %s at %s%% power.\n", name(), target->name(),
325 power);
326 TWCAttackResults res = Damaged;
328 int attack_roll = rand() % 100;
329 game->log("Attacker rolls %s\n", attack_roll);
330 if (attack_roll < weaponclass()->accuracy())
332 // Hit!
333 int mean_damage = weaponclass()->damage();
334 int damage = (int)box_muller(mean_damage, mean_damage * 0.1) * power
335 / 100;
336 if (!damage)
337 damage = 1;
339 bool still_alive = target->damage_ship(damage);
341 game->log("Attacker hit for %s damage (mean %s).\n", damage,
342 mean_damage);
344 sector()->announce(ShipAttackHit, id, target->id,
345 WvString("%s blasts %s with its %s.\n",
346 name(), target->name(),
347 weaponclass()->name()),
348 captain()->player.id);
350 if (!still_alive)
351 res = Destroyed;
353 else
355 res = Missed;
356 game->log("attacker missed.\n");
357 sector()->announce(ShipAttackMissed, id, target->id,
358 WvString("%s misses %s with its %s.\n",
359 name(), target->name(),
360 weaponclass()->name()),
361 captain()->player.id);
362 captain()->msg("We missed, captain.\n");
365 cfg["weapon charged"].setmeint(0);
366 game->events.add_event(TWCEventCallback(this, &TWCShip::charge_weapon),
367 0, weaponclass()->recharge_time(), true);
369 return res;
373 bool TWCShip::damage_ship(int dmg)
375 decr_uniconf(cfg["shield strength"], dmg);
376 if (shield_strength() < 0)
378 decr_uniconf(cfg["hull strength"], shield_strength() * -1);
379 cfg["shield strength"].setmeint(0);
381 if (hull_strength() <= 0)
383 sector()->announce(ShipDestroyed, id, 0, WvString("The %s explodes!\n",
384 name()),
385 captain() ? captain()->player.id : 0);
386 if (captain())
388 captain()->msg("Can't... hold it... together...\n");
389 captain()->die();
392 game->remove_ship(this);
393 return false;
396 if (captain())
397 if (shield_strength())
398 captain()->msg(WvString("Shields at %s percent, captain.\n",
399 shield_strength() * 100 /
400 shieldclass()->strength()));
401 else
402 captain()->msg(WvString("Hull at %s percent, captain.\n",
403 hull_strength() * 100 /
404 shipclass()->hull_strength()));
406 return true;
410 void TWCShip::do_warp(int)
412 sector()->ship_warp_out(this);
413 cfg["location"].setme(warping_to->coord);
414 warping_to = NULL;
415 sector()->ship_warp_in(this);
416 post_warp(0);
420 void TWCShip::start_warp(TWCEventCallback _post_warp, TWCSector *dest)
422 post_warp = _post_warp;
423 warping_to = dest;
424 game->events.add_event(TWCEventCallback(this, &TWCShip::do_warp), 0, 2,
425 true);
429 void TWCShip::set_by_player(TWCPlayerClient *plyrcli)
431 set_captain(plyrcli);
432 docked_with = NULL;
436 bool TWCShip::check_claim_ownership(TWCPlayer *player)
438 TWCPlayer *oldowner = owner();
439 if (!oldowner || oldowner != player)
441 oldowner->remove_ship(this);
442 cfg["owner"].setmeint(player->id);
443 player->add_ship(this);
444 return true;
446 return false;
450 TWCShipIf::TWCShipIf(TWCShip &_ship, TWCHumanPlayerClient &_plyrcli)
451 : TWCObjectIf(_ship, _plyrcli), ship(_ship), autopilot(false),
452 tactical(false), transporter(false), final_dest(NULL)
454 sector_scan();
458 TWCShipIf::~TWCShipIf()
460 if (final_dest)
461 delete final_dest;
465 void TWCShipIf::print_menu()
467 if (final_dest)
468 return;
470 if (tactical)
471 plyrcli.print(ship.game->get_screen("tactical"));
472 else if (transporter)
473 plyrcli.print(ship.game->get_screen("transporter"));
474 else
475 plyrcli.print(ship.game->get_screen("ship"));
479 void TWCShipIf::menu_input()
481 if (final_dest)
483 warping_menu_input();
484 return;
486 else if (tactical)
488 tactical_menu_input();
489 return;
491 else if (transporter)
493 transporter_menu_input();
494 return;
497 char *line = plyrcli.getline(0);
498 if (!line)
499 return;
501 trim_string(line);
503 switch (line[0])
505 case 's':
506 case 'S':
507 if (ship.are_shields_up())
509 plyrcli.print("You lower your ship's shields.\n");
510 ship.lower_shields();
512 else
514 plyrcli.print("You raise your ship's shields.\n");
515 ship.raise_shields();
517 break;
519 case 'm':
520 case 'M':
522 plyrcli.print("To which sector (x y z)? ");
523 line = plyrcli.continue_getline();
524 if (!line)
525 return;
526 ship_move(TWCSectorCoord(trim_string(line)));
527 break;
530 case 'p':
531 case 'P':
533 if (!plyrcli.player.current_turns())
535 plyrcli.print("You don't have enough turns left.\n");
536 return;
539 TWCDockResults res = ship.dock();
540 if (res == DockSuccess)
542 ship.docked_with = ship.sector()->port();
543 plyrcli.print("Docked with %s.\n", ship.docked_with->name());
544 plyrcli.player.deduct_turns(1);
546 else if (res == NoPort)
547 plyrcli.print("\e[1;31mMove along... nothing to dock with "
548 "here.\e[0;37m\n");
549 else
550 plyrcli.print("Only one ship can be docked with trading ports at a "
551 "time.\n");
553 break;
556 case 'a':
557 case 'A':
558 tactical = true;
559 break;
561 case 'l':
562 case 'L':
563 break;
565 case 'd':
566 case 'D':
567 sector_scan();
568 break;
570 case 'c':
571 case 'C':
572 break;
574 case '/':
576 plyrcli.print("\n\n");
577 plyrcli.print("\e[0;32mStats\e[0;37m\n");
578 plyrcli.print("\e[1;33m-=-=-\e[0;37m\n");
579 plyrcli.print("\e[0;32mMoves: \e[1;33m%s\e[0;32m/\e[1;33m%s\n"
580 "\e[0;32mExperience: \e[1;33m%s\n"
581 "\e[0;32mCash: \e[1;33m%s\n"
582 "\e[0;32mCurrent Ship: \e[1;33m%s \e[0;32m(\e[0;33m%s"
583 "\e[0;32m)\e[0;37m\n"
584 "\e[0;32mShield: \e[1;33m%s %s\e[0;32m/\e[1;33m%s\n"
585 "\e[0;32mWeapon: \e[1;33m%s\e[0;37m\n\n"
586 "Press Enter to continue...\n",
587 plyrcli.player.current_turns(), plyrcli.player.max_turns(),
588 plyrcli.player.experience(),
589 ship.game->price_string(plyrcli.player.cash()),
590 ship.name(), ship.shipclass()->name(),
591 ship.shieldclass() ? ship.shieldclass()->name() : WvString("none"),
592 ship.shieldclass() ? ship.shield_strength() : WvString(0),
593 ship.shieldclass() ? ship.shieldclass()->strength() : WvString(0),
594 ship.weaponclass() ? ship.weaponclass()->name() : WvString("none")
596 plyrcli.continue_getline();
598 break;
601 case 'x':
602 case 'X':
603 transporter = true;
604 break;
606 case 'n':
607 case 'N':
609 TWCShip *target = plyrcli.acquire_target("Tow which ship",
610 "You cannot scan a docked "
611 "ship.");
612 if (target)
613 plyrcli.display_inventory(target);
614 break;
617 case 't':
618 case 'T':
620 TWCShip *towing = ship.towing();
621 if (towing)
623 plyrcli.print("Stop towing the %s? ", towing->name());
624 line = plyrcli.continue_getline();
625 if (!line)
626 return;
627 line = trim_string(line);
628 if (line[0] == 'y' || line[0] == 'Y')
630 plyrcli.print("Tractor beam deactivated.\n");
631 ship.tow(NULL);
633 return;
636 TWCShip *target = plyrcli.acquire_target("Tow which ship",
637 "You cannot tow a docked "
638 "ship.");
639 if (!target)
640 return;
642 if (target->captain())
644 plyrcli.print("You can only tow ships without an active "
645 "captain.\n");
646 return;
649 if (ship.tow(target))
651 int move_cost = ship.shipclass()->move_cost() +
652 target->shipclass()->move_cost();
654 plyrcli.print("You are now towing the %s. Your new movement cost "
655 "is %s turns per sector.\n", target->name(),
656 move_cost);
658 else
659 plyrcli.print("Couldn't tow the %s.\n", target->name());
661 break;
664 case 'q':
665 case 'Q':
666 ship.set_captain(NULL);
667 plyrcli.set_location(ship.game->nowhere());
668 break;
670 case '?':
671 plyrcli.print(ship.game->screen("ship"));
672 break;
674 default:
675 break;
680 WvString TWCShipIf::prompt()
682 if (final_dest)
683 return WvString("");
685 return WvString("%s< %sShip %s> %s%s%s/%s%s%s%s", ANSI_YELLOW,
686 ANSI_GREEN, ANSI_YELLOW, ANSI_GREEN,
687 plyrcli.player.current_turns(), ANSI_YELLOW, ANSI_GREEN,
688 plyrcli.player.max_turns(), ANSI_RED,
689 ship.cfg["shields up"].getmeint() ? "" : " *shields down*");
693 void TWCShipIf::ship_move(const TWCSectorCoord dest_sector)
695 if (!dest_sector.ok)
697 plyrcli.print("Invalid sector.\n");
698 return;
701 TWCSectorCoord cur_sector = ship.sector()->coord;
702 if (cur_sector == dest_sector)
704 plyrcli.print("You're already in that sector.\n");
705 return;
708 // check to see if the destination actually exists
709 TWCSectorCoord next_hop = ship.game->find_route(cur_sector, dest_sector);
710 if (next_hop == cur_sector)
712 plyrcli.print("Sector %s does not exist.\n", dest_sector);
713 return;
716 // if no match was found (above) then we need to
717 // find a path to the destination
718 if (next_hop != dest_sector)
720 TWCSectorCoord chain = next_hop;
722 plyrcli.print("Sector %s not adjacent; calculating route.\n[%s] ",
723 dest_sector, next_hop);
725 while (chain != dest_sector)
727 chain = ship.game->find_route(chain, dest_sector);
728 plyrcli.print("[%s] ", chain);
731 char *line;
732 plyrcli.print("\nEngage [yna]? ");
733 line = plyrcli.continue_getline();
734 if (!line)
735 return;
736 trim_string(line);
737 if (line[0] == 'a' || line[0] == 'A')
738 autopilot = true;
739 else if (line[0] != 'y' && line[0] != 'Y')
740 return;
743 final_dest = new TWCSectorCoord(dest_sector);
744 if (checkmoveturns())
745 ship.start_warp(TWCEventCallback(this, &TWCShipIf::post_warp),
746 ship.game->get_sector(next_hop));
751 void TWCShipIf::warping_menu_input()
753 char *line = plyrcli.getline(0);
754 if (!line || ship.warping())
755 return;
756 trim_string(line);
758 switch (line[0])
760 case 'a':
761 case 'A':
762 autopilot = true;
763 // fall through
764 case 'y':
765 case 'Y':
767 TWCSectorCoord next_hop = ship.game->find_route(ship.sector()->coord,
768 *final_dest);
769 ship.start_warp(TWCEventCallback(this, &TWCShipIf::post_warp),
770 ship.game->get_sector(next_hop));
771 break;
773 case 'n':
774 case 'N':
775 // ship.stop_warp();
776 plyrcli.alarm(0);
777 final_dest = NULL;
778 break;
779 default:
780 plyrcli.print("Continue with trip? [yna]");
785 void TWCShipIf::post_warp(int)
787 sector_scan();
788 if (ship.sector()->coord != *final_dest)
790 TWCSectorCoord next_hop = ship.game->find_route(ship.sector()->coord,
791 *final_dest);
792 if (!autopilot)
793 plyrcli.print("Continue with trip [yna]? ");
794 else
795 ship.start_warp(TWCEventCallback(this, &TWCShipIf::post_warp),
796 ship.game->get_sector(next_hop));
797 return;
800 plyrcli.alarm(0);
801 final_dest = NULL;
805 void TWCShipIf::sector_scan()
807 plyrcli.print("\n\e[1;33m-=- \e[0;35m %s \e[1;33m-=-\e[0;37m\n",
808 ship.sector()->name());
810 TWCShipList ships;
811 ship.sector()->find_player_ships(ships, &ship);
812 if (ships.count())
814 plyrcli.print("Ships in this sector:\n");
815 TWCShipList::Iter shi(ships);
816 for (shi.rewind(); shi.next(); )
817 plyrcli.print("%s%s <%s>, %s\n", shi.ptr() == ship.towing() ?
818 "<TOWING> " : "", shi().name(),
819 shi().shipclass()->name(),
820 shi().captain() ? shi().captain()->player.name()
821 : WvString("unoccupied"));
824 TWCPort *port = ship.sector()->port();
825 if (port)
826 plyrcli.print("A spaceport is in this sector: %s\n", port->name());
830 void TWCGameController::initialize_ship(int ship, int captain)
832 UniConf shipcfg(cfg["ships"][ship]);
833 shipcfg["captain"].setmeint(captain);
834 shipcfg["shields up"].setmeint(1);
835 shipcfg["weapon recharged"].setmeint(1);
839 bool TWCShipIf::checkmoveturns()
841 int move_cost = ship.shipclass()->move_cost();
842 if (ship.towing())
843 move_cost += ship.towing()->shipclass()->move_cost();
845 if (plyrcli.player.current_turns() < move_cost)
847 plyrcli.print("You don't have enough turns left.\n");
848 return false;
850 else
852 plyrcli.player.deduct_turns(move_cost);
853 plyrcli.print("%s turn%s deducted; %s remaining.\n", move_cost,
854 move_cost == 1 ? "" : "s",
855 plyrcli.player.current_turns());
856 plyrcli.print("Warping...\n");
858 return true;
862 void TWCShipIf::tactical_menu_input()
864 char *line = plyrcli.getline(0);
865 if (!line)
866 return;
868 trim_string(line);
870 switch (line[0])
872 case '?':
873 plyrcli.print(ship.game->screen("tactical"));
874 break;
876 case 'a':
877 case 'A':
879 if (!ship.weaponclass())
881 plyrcli.print("You have no weapon on this ship.\n");
882 return;
885 if (!ship.weapon_charged())
887 plyrcli.print("Your weapon is still charging.\n");
888 return;
891 int attack_cost = ship.shipclass()->attack_cost();
892 if (plyrcli.player.current_turns() < attack_cost)
894 plyrcli.print("Sorry, you don't have any turns left.\n");
895 return;
898 TWCShip *target = plyrcli.acquire_target(
899 "Target which ship", "The ship is docked. You can wait until the "
900 "captain returns or attack the port itself."
902 if (!target)
903 return;
905 plyrcli.print("Power setting (1-100)? [100] ");
906 bool abort = false;
907 int power = plyrcli.get_int(abort, 100);
908 if (abort)
909 return;
911 if (power < 1 || power > 100)
913 plyrcli.print("Power setting must be between 1 and 100.\n");
914 return;
917 TWCAttackResults res = ship.shoot(target, power);
919 switch (res)
921 case Damaged:
922 plyrcli.print("Their shields are still holding, captain.\n");
923 break;
925 case Destroyed:
926 plyrcli.print("We destroyed them!\n");
928 default:
929 break;
932 if (res != Aborted)
934 plyrcli.print("Deducting %s turn%s.\n", attack_cost,
935 attack_cost == 1 ? "" : "s");
936 plyrcli.player.deduct_turns(attack_cost);
939 break;
940 #if 0
941 case 'd':
942 case 'D':
944 int target = acquire_target("Demand surrender from which ship",
945 "This ship is currently docked. You can "
946 "wait for the captain to return.");
947 if (!target)
948 return;
950 UniConf targetcfg(cfg["ships"][target]);
951 int target_player_id = targetcfg["captain"].getmeint();
952 UniConf targetpcfg(cfg["players"][target_player_id]);
954 TWCPlayerClient *target_player = game->players[target_player_id];
955 if (!target_player)
957 print("The ship is unoccupied. You'll have to take it by force.\n");
958 return;
961 msg("You demand the surrender of %s.\n", targetcfg["name"].getme());
962 demanded_surrender = target;
963 target_player->event(DemandSurrender, loccfg().key().printable().num(),
964 target,
965 WvString("\n%s demands you drop your shields and "
966 "surrender your cargo! You have 10 "
967 "seconds to comply.",
968 loccfg()["name"].getme()));
969 game->events.add_event(
970 TWCEventCallback(this, &TWCHumanPlayerClient::surrender_timeout),
971 0, 10, true
973 break;
975 #endif
976 case 'q':
977 case 'Q':
978 tactical = false;
979 break;
980 default:
981 break;