Skip to content

Commit

Permalink
Merge pull request #474 from BitLucid/npc_robust_tests
Browse files Browse the repository at this point in the history
Npc robust tests; implemented npc trait "rich"
  • Loading branch information
Beagle committed Feb 12, 2016
2 parents 70f8d36 + 632066a commit 044cc9a
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 29 deletions.
34 changes: 20 additions & 14 deletions deploy/lib/control/NpcController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class NpcController { //extends controller
const ALIVE = true;
const PRIV = false;
const HIGH_TURNS = 50;
const ITEM_DECREASES_GOLD_FACTOR = 0.9;
const ITEM_DECREASES_GOLD_DIVISOR = 1.11;
const ONI_DAMAGE_CAP = 20;
const RANDOM_ENCOUNTER_DIVISOR = 400;
const SAMURAI_REWARD_DMG = 100;
Expand Down Expand Up @@ -109,14 +109,22 @@ private function setThiefCounter($num) {
* The reward for defeating an npc, less if items popped
*
* @param Npc $npco
* @param boolean $reward_gold
* @param boolean $reward_item Any items were rewarded.
* @return int
* @note
* If npc gold explicitly set to 0, reward gold will be totally skipped
* "rich" npcs will have a higher gold minimum
*/
private function calcRewardGold(Npc $npco, $reward_item) {
// Hack a little off reward gold if items received.
return ($npco->gold() === 0 ? 0 : ((bool)$reward_item ? floor($npco->gold() * self::ITEM_DECREASES_GOLD_FACTOR) : $npco->gold()));
private function calcReceivedGold(Npc $npco, $reward_item) {
if($npco->gold() === 0){ // These npcs simply don't give gold.
return 0;
}
// Hack a little off max gold if items received.
$divisor = 1;
if($reward_item){
$divisor = self::ITEM_DECREASES_GOLD_FACTOR;
}
return rand($npco->min_gold(), floor($npco->gold()/$divisor));
}

/**
Expand All @@ -127,16 +135,14 @@ private function calcRewardGold(Npc $npco, $reward_item) {
* @param Array $npcs
* @return array [$npc_template, $combat_data]
*/
private function attackAbstractNpc($victim, $player, $npcs) {
private function attackAbstractNpc($victim, Player $player, $npcs) {
$npc_stats = $npcs[$victim]; // Pull an npcs individual stats with generic fallbacks.
$npco = new Npc($npc_stats); // Construct the npc object.
$display_name = first_value((isset($npc_stats['name']) ? $npc_stats['name'] : null), ucfirst($victim));
$status_effect = (isset($npc_stats['status']) ? $npc_stats['status'] : null);
// TODO: Calculate and display damage verbs
$reward_item = (isset($npc_stats['item']) && $npc_stats['item'] ? $npc_stats['item'] : null);
$npc_gold = (int) (isset($npc_stats['gold']) ? $npc_stats['gold'] : 0);
$is_quick = (boolean) ($npco->speed() > $player->speed()); // Beyond basic speed and they see you coming, so show that message.
$reward_gold = $this->calcRewardGold($npco, (bool) $reward_item);
$bounty_mod = (isset($npc_stats['bounty']) ? $npc_stats['bounty'] : null);
$is_weaker = ($npco->strength() * 3) < $player->strength(); // Npc much weaker?
$is_stronger = ($npco->strength()) > ($player->strength() * 3); // Npc More than twice as strong?
Expand All @@ -160,12 +166,11 @@ private function attackAbstractNpc($victim, $player, $npcs) {
// ******* FIGHT Logic ***********
$npc_damage = $npco->damage(); // An instance of damage.
$survive_fight = $player->vo->health = subtractHealth($player->id(), $npc_damage);
// TODO: make $armored = $npco->has_trait('armored')? 1 : 0;
$kill_npc = ($npco->health() < $player->damage());

if ($survive_fight > 0) {
// The ninja survived, they'll get gold.
$received_gold = rand(floor($reward_gold/5), $reward_gold);
// The ninja survived, they get any gold the npc has.
$received_gold = $this->calcReceivedGold($npco, (bool) $reward_item);
add_gold($player->id(), $received_gold);
$received_display_items = array();

Expand All @@ -185,12 +190,12 @@ private function attackAbstractNpc($victim, $player, $npcs) {
$player->vo->level > self::MIN_LEVEL_FOR_BOUNTY &&
$player->vo->level <= self::MAX_LEVEL_FOR_BOUNTY
) {
$added_bounty = floor($attacker_level / 3 * $bounty_mod);
$added_bounty = floor($player->level() / 3 * $bounty_mod);
addBounty($player->id(), ($added_bounty));
}
}

$is_rewarded = (bool) $reward_gold || (bool)count($received_display_items);
$is_rewarded = (bool) $received_gold || (bool)count($received_display_items);

if (isset($npc_stats['status']) && null !== $npc_stats['status']) {
$player->addStatus($npc_stats['status']);
Expand Down Expand Up @@ -479,7 +484,7 @@ public function attack() {
$this->setThiefCounter($counter+1); // Incremement the current state of the counter.

if ($counter > 20 && rand(1, 3) == 3) {
// Only after many attacks do you have the chance to be attacked back by the group of theives.
// Only after many attacks do you have the chance to be attacked back by the group of thieves.
$this->setThiefCounter(0); // Reset the counter to zero.
$group_attack= rand(50, 150);

Expand Down Expand Up @@ -546,6 +551,7 @@ public function attack() {

// Uses a sub-template inside for specific npcs.
$parts = [
'victim' => $victim, // merge may override in theory
'npc_template' => $npc_template,
'attacked' => 1,
'turns' => $player->turns(),
Expand Down
15 changes: 12 additions & 3 deletions deploy/lib/data/Npc.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
class Npc implements Character{
private $data;
const RICH_MIN_GOLD_DIVISOR = 1.3;
const MIN_GOLD = 0; // Could become data driven later

public function __construct($content){
if(is_string($content) && trim($content)){
Expand Down Expand Up @@ -166,9 +168,16 @@ public function dynamicBounty(Player $char){
}

/**
* Presumably this is modified gold.
**/
public function gold(){
* Max gold
*/
public function gold() {
return $this->gold;
}

/**
* Get min gold for an npc.
*/
public function min_gold() {
return (int) ($this->has_trait('rich') ? floor($this->gold()/self::RICH_MIN_GOLD_DIVISOR) : self::MIN_GOLD);
}
}
6 changes: 3 additions & 3 deletions deploy/lib/data/NpcFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ public static function npcsData() {
'nureonna'=>['name'=>'Nureonna', 'strength'=>30, 'speed'=>50, 'stamina'=>40, 'img'=>'nureonna', 'race'=>'yokai', 'img'=>'nureonna.jpg', 'status'=>POISON, 'inventory'=>['charcoal'=>1], 'traits'=>'poisonous'],
'tengu'=>['name'=>'Tengu', 'short'=>'is a large winged demon', 'strength'=>70, 'speed'=>20, 'stamina'=>100, 'race'=>'tengu', 'inventory'=>['tetsubo'=>'.05']],
'ushioni'=>['name'=>'Ushi-Oni', 'strength'=>90, 'stamina'=>130, 'speed'=>50, 'race'=>'ushioni', 'img'=>'ushioni2.jpg'],
'ryu'=>['name'=>'Ryu', 'strength'=>150, 'stamina'=>200, 'speed'=>190, 'short'=>'is a serpent-dragon, with the gleam of intelligence in it\'s eyes and the glint of death on it\'s claws', 'img'=>'hokusai-dragon.jpg', 'race'=>'ryu', 'traits'=>'armored'],
'ryu'=>['name'=>'Ryu', 'strength'=>150, 'stamina'=>200, 'speed'=>190, 'short'=>'is a serpent-dragon, with the gleam of intelligence in it\'s eyes and the glint of death on it\'s claws', 'img'=>'hokusai-dragon.jpg', 'race'=>'ryu', 'traits'=>'armored,rich'],
];

if (defined('DEBUG') && DEBUG) {
$npcs += [
'peasant2'=>['name'=>'Peasant', 'race'=>'human', 'img'=>'fighter.png', 'strength'=>5, 'stamina'=>5, 'speed'=>5, 'ki'=>1, 'damage'=>1, 'gold'=>20, 'bounty'=>1, 'traits'=>'villager,sometimes_disguised_ninja', 'inventory'=>['kunai'=>'.01', 'shuriken'=>'.01']],
'merchant2'=>['name'=>'Merchant', 'race'=>'human', 'strength'=>10, 'stamina'=>20, 'speed'=>10, 'ki'=>1,
'damage'=>15, 'gold'=>50, 'bounty'=>5, 'img'=>'merchant.png', 'inventory'=>['phosphor'=>'.3'], 'traits'=>'villager'],
'damage'=>15, 'gold'=>50, 'bounty'=>5, 'img'=>'merchant.png', 'inventory'=>['phosphor'=>'.3'], 'traits'=>'villager,rich'],
'guard2'=>['name'=>'Guard', 'short'=>'is a member of the ashigaru foot soldiers, hired for various tasks', 'race'=>'human', 'strength'=>'30', 'stamina'=>30, 'speed'=>12, 'ki'=>1,
'damage'=>0, 'gold'=>50, 'bounty'=>10, 'img'=>'guard.png', 'inventory'=>['ginsengroot'=>'.2'], 'traits'=>'partial_match_strength'],
'monk'=>['name'=>'Monk', 'strength'=>10, 'stamina'=>10, 'speed'=>10, 'ki'=>30, 'race'=>'human', 'inventory'=>['prayerwheel'=>'.2'], 'traits'=>'deflection,defensive,self_heal'],
Expand All @@ -196,7 +196,7 @@ public static function npcsData() {
'ox'=>['name'=>'Ox', 'short'=>'', 'strength'=>50, 'speed'=>20, 'stamina'=>40, 'damage'=>20, 'race'=>'animal'],
'dog'=>['name'=>'Dog', 'short'=>'barks wildly', 'strength'=>20, 'speed'=>20, 'stamina'=>10, 'damage'=>10, 'race'=>'animal'],
'tiger'=>['name'=>'Tiger', 'short'=>'circles in for the kill', 'strength'=>60, 'speed'=>60, 'stamina'=>60, 'damage'=>60, 'race'=>'animal'],
'basan'=>['name'=>'Basan', 'img'=>'basan.jpg', 'strength'=>1, 'stamina'=>1, 'speed'=>1], // Uses default race of: creature.
'basan'=>['name'=>'Basan', 'img'=>'basan.jpg', 'strength'=>1, 'stamina'=>1, 'speed'=>1, 'traits'=>'poisonous'], // Uses default race of: creature.
'kamaitachi'=>['name'=>'Kama-itachi', 'img'=>'kamaitachi.jpg', 'race'=>'yokai'],
'nuribotoke'=>['name'=>'Nuri-Botoke', 'img'=>'nuribotoke.jpg', 'race'=>'yokai'],
'hitodama'=>['name'=>'Hitodama', 'short'=>'are spirit orbs of fire', 'img'=>'hitodama.gif', 'race'=>'kami', 'traits'=>'whispy'],
Expand Down
2 changes: 1 addition & 1 deletion deploy/resources.build.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
define('DATABASE_PASSWORD', "unused_in_build"); // *** The password for the database connection, trust on dev
define('DATABASE_NAME', "nw"); // *** The name of the database to connect to, nw on dev
define('OFFLINE', false); // *** Controls if remote or local resources are used
define('DEBUG', false); // *** Shorter debugging constant name, set as false on live.
define('DEBUG', true); // *** Shorter debugging constant name, set as false on live.
define('PROFILE', false); // *** Whether or not to do performance profiling
define('DEBUG_ALL_ERRORS', false); // *** Second debugging level, e.g. email debugging, only works when debug is also on.
define('SERVER_ROOT', realpath(__DIR__).'/'); // *** The root deployment directory of the game
Expand Down
96 changes: 88 additions & 8 deletions deploy/tests/integration/controller/NpcControllerTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace tests\integration\controller;

use NinjaWars\core\data\Npc;
use NinjaWars\core\control\NpcController;
use NinjaWars\core\extensions\SessionFactory;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -9,30 +10,30 @@
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use \TestAccountCreateAndDestroy as TestAccountCreateAndDestroy;
use \PHPUnit_Framework_TestCase as PHPUnit_Framework_TestCase;
use \Player;

class NpcControllerTest extends PHPUnit_Framework_TestCase {
function setUp() {
SessionFactory::init(new MockArraySessionStorage());

protected function setUp() {
$this->char = TestAccountCreateAndDestroy::char();

SessionFactory::init(new MockArraySessionStorage());
SessionFactory::getSession()->set('player_id', $this->char->id());
$this->controller = new NpcController([
'randomness' => function(){return 0;}
]);
}

function tearDown() {
protected function tearDown() {
TestAccountCreateAndDestroy::destroy();
$session = SessionFactory::getSession();
$session->invalidate();
}

function testControllerIndexDoesntError() {
public function testControllerIndexDoesntError() {
$response = $this->controller->index();
$this->assertNotEmpty($response);
}

function testControllerGetRandomness() {
public function testControllerGetRandomnessDoesntError() {
$this->controller = new NpcController([
'char_id' => ($this->char->id()),
'randomness' => function(){return 0;}
Expand All @@ -42,9 +43,88 @@ function testControllerGetRandomness() {
$this->assertNotEmpty($response);
}

function testControllerAttackAsIfAgainstAPeasant() {
public function testSessionHasPlayerId(){
$this->assertEquals($this->char->id(), SessionFactory::getSession()->get('player_id'));
}

public function testControllerAttackAsIfAgainstAPeasant() {
$_SERVER['REQUEST_URI'] = '/npc/attack/peasant';
$initial_health = $this->char->health();
$response = $this->controller->attack();
$final_char = Player::find($this->char->id());
$this->assertNotEmpty($response);
$this->assertEquals('peasant', $response['parts']['victim']);
}

public function testAttackPeasantWithABountableHighLevelCharacter() {
$_SERVER['REQUEST_URI'] = '/npc/attack/peasant';
// Bump the test player's level for bounty purposes.
$this->char->vo->level = 20;
$this->char->save();
$response = $this->controller->attack();
$this->assertNotEmpty($response);
$final_char = Player::find($this->char->id());
$this->assertGreaterThan(0, $final_char->bounty());
}

public function testControllerAttackAsIfAgainstAPeasant2() {
$_SERVER['REQUEST_URI'] = '/npc/attack/peasant2';
$initial_health = $this->char->health();
$response = $this->controller->attack();
$final_char = Player::find($this->char->id());
$this->assertNotEmpty($response);
$this->assertEquals('peasant2', $response['parts']['victim']);
$this->assertGreaterThan(0, $final_char->health());
}

public function testControllerAttackAsIfAgainstAMerchant() {
$_SERVER['REQUEST_URI'] = '/npc/attack/merchant';
$response = $this->controller->attack();
$this->assertEquals('merchant', $response['parts']['victim']);
}

public function testControllerAttackAsIfAgainstAMerchant2() {
$_SERVER['REQUEST_URI'] = '/npc/attack/merchant2';
$init_gold = $this->char->gold();
$npco = new Npc('merchant2');
$response = $this->controller->attack();
$final_char = Player::find($this->char->id());
$this->assertNotEmpty($response);
$this->assertEquals('merchant2', $response['parts']['victim']);
$this->assertGreaterThan(0, $npco->min_gold());
$this->assertGreaterThan($init_gold, $final_char->gold());
}

public function testControllerAttackAsIfAgainstAGuard() {
$_SERVER['REQUEST_URI'] = '/npc/attack/guard';
$response = $this->controller->attack();
$this->assertNotEmpty($response);
$this->assertEquals('guard', $response['parts']['victim']);
}

public function testControllerAttackAsIfAgainstAGuard2() {
$_SERVER['REQUEST_URI'] = '/npc/attack/guard2';
$response = $this->controller->attack();
$this->assertNotEmpty($response);
$this->assertEquals('guard2', $response['parts']['victim']);
}

public function testControllerAttackAsIfAgainstAThief() {
$_SERVER['REQUEST_URI'] = '/npc/attack/thief';
$response = $this->controller->attack();
$this->assertEquals('thief', $response['parts']['victim']);
}

public function testControllerAttackAsIfAgainstAThief2() {
$this->markTestIncomplete('There is not yet a thief2, but turn this on when there is.');
$_SERVER['REQUEST_URI'] = '/npc/attack/thief2';
$response = $this->controller->attack();
$this->assertEquals('theif2', $response['parts']['victim']);
}

public function testControllerAttackAsIfAgainstASamura() {
$_SERVER['REQUEST_URI'] = '/npc/attack/samurai';
$response = $this->controller->attack();
$this->assertEquals('samurai', $response['parts']['victim']);
}
}

0 comments on commit 044cc9a

Please sign in to comment.