bazaar re-init
Krzysztof Sikorski

Krzysztof Sikorski commited on 2010-11-18 23:01:29
Showing 269 changed files, with 16178 additions and 0 deletions.

... ...
@@ -0,0 +1,6 @@
1
+./cfg/*
2
+./log/*
3
+./tmp/*
4
+./lib/PHPTAL*
5
+./public/google*
6
+./public/scyzoryk/.htaccess
... ...
@@ -0,0 +1,182 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+chdir(dirname(__FILE__));
4
+$cfg = require_once './public/_init.php';
5
+ini_set('expose_php', '0');
6
+ini_set('default_mimetype', '');
7
+ini_set('default_charset', '');
8
+$dbClient = Daemon::createDbClient($cfg);
9
+$dbCfg = new Daemon_DbConfig($dbClient);
10
+$forum = new Daemon_Forum($dbClient);
11
+
12
+//cleanup
13
+$queries = array(
14
+	"CREATE TEMPORARY TABLE junk(id INT NOT NULL PRIMARY KEY)",
15
+	//delete inactive players
16
+	"INSERT INTO junk(id) SELECT player_id FROM players WHERE last_login < (NOW() - INTERVAL 1 MONTH)",
17
+	"DELETE FROM players WHERE player_id IN (SELECT id FROM junk)",
18
+	"DELETE FROM user_agents WHERE player_id IN (SELECT id FROM junk)",
19
+	"UPDATE characters SET player_id = NULL WHERE player_id IN (SELECT id FROM junk)",
20
+	"TRUNCATE TABLE junk",
21
+	//delete inactive characters
22
+	"INSERT INTO junk(id) SELECT character_id FROM characters WHERE player_id IS NULL OR last_action < (NOW() - INTERVAL 1 MONTH)",
23
+	"DELETE FROM characters WHERE character_id IN (SELECT id FROM junk)",
24
+	"DELETE FROM character_data WHERE character_id IN (SELECT id FROM junk)",
25
+	"DELETE FROM character_missions WHERE character_id IN (SELECT id FROM junk)",
26
+	"DELETE FROM character_regions WHERE character_id IN (SELECT id FROM junk)",
27
+	"DELETE FROM character_statistics WHERE character_id IN (SELECT id FROM junk)",
28
+	"DELETE FROM character_titles WHERE character_id IN (SELECT id FROM junk)",
29
+	"DELETE FROM combat_units WHERE combat_unit_id IN (SELECT CONCAT('character-', id) FROM junk)",
30
+	"DELETE FROM inventory WHERE character_id IN (SELECT id FROM junk)",
31
+	"UPDATE clans SET leader_id = NULL WHERE leader_id IN (SELECT id FROM junk)",
32
+	"DELETE FROM clan_invitations WHERE character_id IN (SELECT id FROM junk)",
33
+	"TRUNCATE TABLE junk",
34
+	//dump abandoned clans & old invitations
35
+	"DELETE FROM clans WHERE leader_id IS NULL",
36
+	"DELETE FROM clan_invitations WHERE date_added < (NOW() - INTERVAL 1 WEEK)",
37
+);
38
+foreach($queries as $sql)
39
+	$dbClient->query($sql, array());
40
+unset($queries);
41
+
42
+//check for endgame
43
+if(!$dbCfg->rolloversEnabled)
44
+	return;
45
+
46
+//create rollover entry
47
+$sql = "INSERT INTO rollovers(date_added) VALUES (now())";
48
+$dbClient->query($sql);
49
+$rolloverId = $dbClient->lastInsertId();
50
+
51
+//give turns
52
+$sql = "UPDATE character_data SET turns = LEAST(:limit, turns + :delta)";
53
+$params = array('delta' => (int) $dbCfg->turnDelta, 'limit' => (int) $dbCfg->turnLimit);
54
+$dbClient->query($sql, $params);
55
+
56
+//run caern sieges
57
+$sql = "SELECT l.location_id FROM locations l JOIN character_data cd USING(location_id)
58
+	WHERE l.type='caern' AND cd.faction_id IS NOT NULL AND cd.faction_id != l.faction_id
59
+	GROUP BY l.location_id";
60
+$locations = $dbClient->selectColumn($sql);
61
+foreach ((array) $locations as $siegeLocationId)
62
+{
63
+	$combat = new Daemon_CaernSiege();
64
+	$combat->attachDbClient($dbClient);
65
+	$combat->execute($siegeLocationId);
66
+	$sql = "INSERT INTO battles(rollover_id, location_id, type, combat_log)
67
+		VALUES (:rolloverId, :locationId, 'caern', :combatLog)";
68
+	$params = array('rolloverId' => $rolloverId, 'locationId' => $siegeLocationId,
69
+		'combatLog' => $combat->getCombatLog());
70
+	$dbClient->query($sql, $params);
71
+	$dbCfg->siegeLocationId = null;
72
+	$siegeLocationId = null;
73
+	unset($combat);
74
+}
75
+
76
+//update faction power
77
+$decay = (float) $dbCfg->factionDecay;
78
+$sql = "UPDATE factions f SET f.power = FLOOR(:decay * f.power) + COALESCE((
79
+	SELECT SUM(l.faction_value) FROM locations l WHERE l.type='caern' AND l.faction_id=f.faction_id
80
+), 0)";
81
+$dbClient->query($sql, array('decay' => $decay));
82
+
83
+//activate bosses
84
+$sql = "SELECT MAX(level) As max_level, MAX(rank_id) AS max_rank FROM character_data";
85
+$row = $dbClient->selectRow($sql, array());
86
+$unlockBosses = ($row['max_level'] >= $dbCfg->bossUnlockLevel) && ($row['max_rank'] >= $dbCfg->bossUnlockRank);
87
+if($unlockBosses)
88
+{
89
+	$dbClient->query("UPDATE locations SET boss_status='active' WHERE type='boss' AND boss_status != 'defeated'");
90
+	if(!$dbCfg->endgame)
91
+		$dbCfg->endgame = 1;
92
+}
93
+
94
+//run boss sieges
95
+$sql = "SELECT location_id, name FROM locations WHERE type='boss' AND boss_status = 'active'";
96
+$locations = $dbClient->selectAll($sql);
97
+if($locations)
98
+{
99
+	$factionPowers = array();
100
+	$sql = "SELECT faction_id, power FROM factions";
101
+	foreach($dbClient->selectAll($sql) as $row)
102
+		$factionPowers[$row['faction_id']] = $row['power'];
103
+	foreach($locations as $row)
104
+	{
105
+		$combat = new Daemon_BossCombat();
106
+		$combat->attachDbClient($dbClient);
107
+		$combat->execute($row['location_id'], $factionPowers);
108
+		$combatLog = $combat->getCombatLog();
109
+		if($combatLog)
110
+		{
111
+			$sql = "INSERT INTO battles(rollover_id, location_id, type, combat_log)
112
+				VALUES (:rolloverId, :locationId, 'boss', :combatLog)";
113
+			$params = array('rolloverId' => $rolloverId, 'locationId' => $row['location_id'], 'combatLog' => $combatLog);
114
+			$dbClient->query($sql, $params);
115
+			$forum->addChat(null, 'public', "Siedziba bossa \"$row[name]\" zaatakowana!");
116
+		}
117
+	}
118
+}
119
+
120
+
121
+//check for ending
122
+$factions = array();
123
+$sql = "SELECT faction_id, name FROM factions";
124
+foreach($dbClient->selectAll($sql) as $row)
125
+	$factions[$row['faction_id']] = array('name' => $row['name'], 'active' => 0, 'defeated' => 0);
126
+$sql = "SELECT faction_id, (boss_status!='defeated') AS active
127
+	FROM locations WHERE type = 'boss' GROUP BY faction_id, active";
128
+foreach($dbClient->selectAll($sql) as $row)
129
+{
130
+	if($row['active'])
131
+		$factions[$row['faction_id']]['active']++;
132
+	else
133
+		$factions[$row['faction_id']]['defeated']++;
134
+}
135
+$active = array();
136
+$defeated = array();
137
+foreach($factions as $factionId => $row)
138
+{
139
+	if($row['active'] || !$row['defeated'])
140
+		$active[$factionId] = $row['name'];
141
+	else
142
+		$defeated[$factionId] = $row['name'];
143
+}
144
+$endgame = (count($active) < 2);
145
+if($endgame)
146
+{
147
+	//final messages
148
+	$active = implode(', ', $active);
149
+	switch($active)
150
+	{
151
+		case 'blue':
152
+			$msg = "Rewolucja została stłumiona. Niech żyje Porządek!";
153
+			break;
154
+		case 'red':
155
+			$msg = "Cesarz został obalony. Niech żyje Rewolucja!";
156
+			break;
157
+		default:
158
+			$msg = "Wojna dobiegła końca, lecz brak w niej zwycięzców. Czas pokaże, kto zajmie miejsce dawnych potęg...";
159
+	}
160
+	$forum->addChat(null, 'public', $msg);
161
+	//cleanup
162
+	$dbCfg->globalMessage = $msg;
163
+	$dbCfg->rolloversEnabled = 0;
164
+	$dbCfg->turnDelta = 0;
165
+	$dbCfg->defaultRespawn = '';
166
+	$sql = "UPDATE character_data SET turns = 0, location_id = NULL";
167
+	$dbClient->query($sql);
168
+	$sql = "TRUNCATE TABLE character_regions";
169
+	$dbClient->query($sql);
170
+}
171
+
172
+//update rollover data
173
+$sql = "SELECT COUNT(1) FROM players";
174
+$nPlayers = $dbClient->selectValue($sql);
175
+$sql = "SELECT COUNT(1) FROM characters";
176
+$nChars = $dbClient->selectValue($sql);
177
+$sql = "SELECT COUNT(1) FROM clans";
178
+$nClans = $dbClient->selectValue($sql);
179
+$sql = "UPDATE rollovers SET players_total = :players, characters_total = :chars,
180
+	clans_total = :clans WHERE rollover_id = :id";
181
+$params = array('id' => $rolloverId, 'players' => $nPlayers, 'chars' => $nChars, 'clans' => $nClans);
182
+$dbClient->query($sql, $params);
... ...
@@ -0,0 +1,102 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//container for miscelaneous functions
4
+class Daemon
5
+{
6
+
7
+
8
+	//class autoloader
9
+	public static function autoload($className)
10
+	{
11
+		$className = mb_strtolower(str_replace('_', DIRECTORY_SEPARATOR, $className));
12
+		spl_autoload($className, '.php');
13
+	}
14
+
15
+
16
+	//creates Daemon_DbClient object using specified config
17
+	public static function createDbClient(Daemon_Config $cfg)
18
+	{
19
+		$dsn = sprintf('mysql:host=%s;dbname=%s', $cfg->dbHost, $cfg->dbSchema);
20
+		$params = array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8', time_zone = '+1:00'");
21
+		$dbh = new PDO($dsn, $cfg->dbUser, $cfg->dbPassword, $params);
22
+		$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
23
+		return new Daemon_DbClient($dbh);
24
+	}
25
+
26
+
27
+	//prepares multiline text for displaying, also inserts some basic tags
28
+	public static function formatMessage($txt, $markup = false)
29
+	{
30
+		$txt = nl2br(htmlspecialchars($txt, ENT_QUOTES));
31
+		if($markup)
32
+		{
33
+			$txt = preg_replace('@\[img\](.+)\[/img\]@uU', '<img src="$1" alt="$1" class="bbcode"/>', $txt);
34
+			$txt = preg_replace('@\[url=(.+)\](.+)\[/url\]@uU', '<a href="$1" rel="nofollow">$2</a>', $txt);
35
+			$txt = preg_replace('@\[url\]([img]){0}(.+)\[/url\]@uU', '<a href="$1" rel="nofollow">$1</a>', $txt);
36
+			$txt = preg_replace('@(^|>|\s)(https://\S+)($|<|\s)@muU', '$1<a href="$2" rel="nofollow">$2</a>$3', $txt);
37
+			$txt = preg_replace('@(^|>|\s)(http://\S+)($|<|\s)@muU', '$1<a href="$2" rel="nofollow">$2</a>$3', $txt);
38
+			$txt = preg_replace('@\[b\](.+)\[/b\]@uU', '<b>$1</b>', $txt);
39
+			$txt = preg_replace('@\[i\](.+)\[/i\]@uU', '<i>$1</i>', $txt);
40
+			$txt = preg_replace('@\[u\](.+)\[/u\]@uU', '<u>$1</u>', $txt);
41
+			$txt = preg_replace('@\[s\](.+)\[/s\]@uU', '<s>$1</s>', $txt);
42
+			$txt = preg_replace('@\[sub\](.+)\[/sub\]@uU', '<sub>$1</sub>', $txt);
43
+			$txt = preg_replace('@\[sup\](.+)\[/sup\]@uU', '<sup>$1</sup>', $txt);
44
+		}
45
+		return $txt;
46
+	}
47
+
48
+
49
+	//returns value from array, or defaults if it doesn't exist
50
+	public static function getArrayValue(array $a, $name, $default = null)
51
+	{
52
+		return isset($a[$name]) ? $a[$name] : $default;
53
+	}
54
+
55
+
56
+	//implodes whitespace, optionally preserving newlines
57
+	public static function normalizeString($string, $preserveNewlines = false)
58
+	{
59
+		$string = str_replace(array("\r\n","\r"), "\n", $string); //unix newlines
60
+		if($preserveNewlines)
61
+			$string = preg_replace('/[^\S\n]+/', ' ', $string);
62
+		else $string = preg_replace('/\s+/', ' ', $string);
63
+		$string = trim($string);
64
+		return $string;
65
+	}
66
+
67
+
68
+	//generates a password hash
69
+	public static function passwordHash($salt, $text)
70
+	{
71
+		return sha1($salt . $text);
72
+	}
73
+
74
+
75
+	//returns random salt for password hashing
76
+	public static function passwordSalt()
77
+	{
78
+		$c0 = ord('0');
79
+		$cA = ord('a');
80
+		$cZ = ord('z');
81
+		$max = 10+$cZ-$cA;
82
+		$salt = '';
83
+		for($i = 0; $i < 8; ++$i)
84
+		{
85
+			$x = mt_rand(0, $max);
86
+			if($x < 10)
87
+				$salt .= chr($c0 + $x);
88
+			else $salt .= chr($cA + $x - 10);
89
+		}
90
+		return $salt;
91
+	}
92
+
93
+
94
+	//redirects to selected url
95
+	public static function redirect($url)
96
+	{
97
+		session_write_close(); //just in case
98
+		header('Content-Type: text/html; charset=UTF-8');
99
+		header(sprintf('Location: %s', $url), true, 303); //"See Other" status
100
+		printf('<!DOCTYPE html><html lang="en"><title>Redirect</title><p><a href="%s">continue</a></p>', $url);
101
+	}
102
+}
... ...
@@ -0,0 +1,100 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+class Daemon_BossCombat extends Daemon_CaernSiege
4
+{
5
+	public function execute($locationId, array $factionPowers = array())
6
+	{
7
+		//read location
8
+		$location = $this->getLocation($locationId);
9
+		$bossFactionId = $location['faction_id'];
10
+		$powerMod = Daemon_Math::factionPowerMult($bossFactionId, $factionPowers);
11
+		//prepare units
12
+		$this->kickNeutrals($locationId);
13
+		$units = $this->getCharacterUnits($locationId);
14
+		//check for empty caern
15
+		if(empty($units))
16
+			return null;
17
+		$attackers = false;
18
+		foreach($units as $factionId => $faction)
19
+		{
20
+			if($factionId != $bossFactionId)
21
+				$attackers = true;
22
+		}
23
+		if(!$attackers)
24
+			return null;
25
+		unset($attackers, $factionId, $faction);
26
+		//prepare boss
27
+		$boss = $this->getBossUnit($location['boss_id'], $bossFactionId, $powerMod);
28
+		if(empty($boss))
29
+			return null;
30
+		$units[$bossFactionId][$boss->_id] = $boss;
31
+		//add monster support
32
+		$supportCount = 0;
33
+		foreach($units as $factionId => $faction)
34
+		{
35
+			if($factionId != $bossFactionId)
36
+				$supportCount += count($faction);
37
+			else
38
+				$supportCount -= count($faction);
39
+		}
40
+		if($supportCount > 0)
41
+		{
42
+			$support = $this->getLocationMonsters($locationId, $bossFactionId, $supportCount);
43
+			foreach($support as $unit)
44
+				$units[$bossFactionId][$unit->_id] = $unit;
45
+		}
46
+		unset($support, $supportCount);
47
+		//execute combat
48
+		$this->combatLog = $this->runCombat($units, $bossFactionId);
49
+		//save characters
50
+		$this->putCharacterUnits($units);
51
+		//find winner
52
+		$winnerId = $this->getWinnerFaction($units);
53
+		//kick losers
54
+		$this->kickEnemies($locationId, $winnerId);
55
+		//update location, send message
56
+		if($winnerId != $bossFactionId)
57
+		{
58
+			$sql = "UPDATE locations SET boss_status='defeated' WHERE location_id=:id";
59
+			$params = array('id' => $locationId);
60
+			$this->dbClient->query($sql, $params);
61
+			$msg = "Boss $boss->name z lokacji $location[name] został pokonany!";
62
+			$forum = new Daemon_Forum($this->dbClient);
63
+			$forum->addChat(null, 'public', $msg);
64
+		}
65
+	}
66
+
67
+
68
+	private function getBossUnit($monsterId, $factionId, $powerMod)
69
+	{
70
+		//read base stats
71
+		$sql = "SELECT monster_id, name, combat_unit_id
72
+			FROM monsters WHERE monster_id=:id";
73
+		$row = $this->dbClient->selectRow($sql, array('id' => $monsterId));
74
+		$unit = new Daemon_Combat_Unit();
75
+		$unit->attachDbClient($this->dbClient);
76
+		$unit->get(array('combat_unit_id' => $row['combat_unit_id']));
77
+		$unit->name = $row['name'];
78
+		$unit->faction_id = $factionId;
79
+		$unit->_id = 'boss';
80
+		//modify stats
81
+		$modified = array(
82
+			'str1', 'atk1', 'str2', 'atk2',
83
+			'pdef', 'pres', 'mdef', 'mres',
84
+			'speed', 'armor', 'regen', 'healthMax',
85
+		);
86
+		foreach($modified as $name)
87
+			$unit->$name *= $powerMod;
88
+		$unit->health = $unit->healthMax;
89
+		return $unit;
90
+	}
91
+
92
+
93
+	private function getLocation($locationId)
94
+	{
95
+		$sql = "SELECT l.faction_id, l.boss_id, l.name, f.name AS faction_name
96
+			FROM locations l JOIN factions f USING(faction_id)
97
+			WHERE location_id = :id";
98
+		return $this->dbClient->selectRow($sql, array('id' => $locationId));
99
+	}
100
+}
... ...
@@ -0,0 +1,270 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+class Daemon_CaernSiege
4
+{
5
+	protected $dbClient = null;
6
+	protected $combatLog;
7
+
8
+
9
+	protected function addCharacters(Daemon_Combat $combat, array $units, $locationFactionId)
10
+	{
11
+		foreach($units as $factionId => $faction)
12
+		{
13
+			foreach($faction as $unit)
14
+			{
15
+				$attacker = ($factionId != $locationFactionId);
16
+				$combat->addUnit($unit->_id, $unit, $attacker);
17
+			}
18
+		}
19
+	}
20
+
21
+
22
+	public function attachDbClient(Daemon_DbClient $dbClient)
23
+	{
24
+		$this->dbClient = $dbClient;
25
+	}
26
+
27
+
28
+	//executes the siege, generates the report
29
+	public function execute($locationId)
30
+	{
31
+		//prepare units
32
+		$this->kickNeutrals($locationId);
33
+		$faction = $this->getLocationFaction($locationId);
34
+		$caernFactionId = $faction['id'];
35
+		$units = $this->getCharacterUnits($locationId);
36
+		//check for empty caern
37
+		if(empty($units))
38
+		{
39
+			$this->combatLog = '<p>Caern utracony z braku obrońców.</p>';
40
+			$winnerId = null;
41
+		}
42
+		else
43
+		{
44
+			//add monster support
45
+			$supportCount = 0;
46
+			foreach($units as $factionId => $faction)
47
+			{
48
+				if($factionId != $caernFactionId)
49
+					$supportCount += count($faction);
50
+				else
51
+					$supportCount -= count($faction);
52
+			}
53
+			if($supportCount > 0)
54
+			{
55
+				$support = $this->getLocationMonsters($locationId, $caernFactionId, $supportCount);
56
+				foreach($support as $unit)
57
+					$units[$caernFactionId][$unit->_id] = $unit;
58
+			}
59
+			unset($support, $supportCount);
60
+			//execute combat
61
+			$this->combatLog = $this->runCombat($units, $caernFactionId);
62
+			//save characters
63
+			$this->putCharacterUnits($units);
64
+			//find winner (units modified by combat)
65
+			$winnerId = $this->getWinnerFaction($units);
66
+			if(!$winnerId)
67
+				$msg = 'utracony';
68
+			elseif($winnerId != $caernFactionId)
69
+				$msg = 'przejęty';
70
+			else
71
+				$msg = 'utrzymany';
72
+			$this->combatLog .= "<p><b>Caern $msg!</b></p>";
73
+		}
74
+		//kick losers
75
+		$this->kickEnemies($locationId, $winnerId);
76
+		//update location
77
+		$sql = "UPDATE locations SET faction_id=:fid WHERE location_id=:id";
78
+		$params = array('fid' => $winnerId, 'id' => $locationId);
79
+		$this->dbClient->query($sql, $params);
80
+	}
81
+
82
+
83
+	protected function getCharacterUnits($locationId)
84
+	{
85
+		$units = array();
86
+		$sql = "SELECT cd.character_id, c.name, cd.faction_id, cd.health, cd.health_max, cd.combat_unit_id
87
+			FROM character_data cd JOIN characters c USING(character_id)
88
+			WHERE location_id=:id AND cd.faction_id IS NOT NULL ORDER BY xp_used DESC";
89
+		$data = $this->dbClient->selectAll($sql, array('id' => $locationId));
90
+		foreach($data as $row)
91
+		{
92
+			$unit = new Daemon_Combat_Unit();
93
+			$unit->attachDbClient($this->dbClient);
94
+			$unit->get(array('combat_unit_id' => $row['combat_unit_id']));
95
+			$unit->name = $row['name'];
96
+			$unit->faction_id = $row['faction_id'];
97
+			$unit->health = $row['health_max'];
98
+			$unit->health_max = $row['health_max'];
99
+			$unit->_id = $row['character_id'];
100
+			$units[$row['faction_id']][$unit->_id] = $unit;
101
+		}
102
+		return $units;
103
+	}
104
+
105
+
106
+	public function getCombatLog()
107
+	{
108
+		return $this->combatLog;
109
+	}
110
+
111
+
112
+	protected function getLocationFaction($locationId)
113
+	{
114
+		$sql = "SELECT faction_id AS id, f.name
115
+			FROM locations l JOIN factions f USING(faction_id)
116
+			WHERE location_id=:id";
117
+		return $this->dbClient->selectRow($sql, array('id' => $locationId));
118
+	}
119
+
120
+
121
+	protected function getLocationMonsters($locationId, $factionId, $desiredCount)
122
+	{
123
+		$result = array();
124
+		$sql = "SELECT m.monster_id, m.name, m.combat_unit_id
125
+			FROM location_monsters lm JOIN monsters m USING(monster_id)
126
+			WHERE location_id=:id";
127
+		$mobs = $this->dbClient->selectAll($sql, array('id' => $locationId));
128
+		if(empty($mobs))
129
+			return array();
130
+		for($i = 0; $i < $desiredCount; ++$i)
131
+		{
132
+			$row = $mobs[array_rand($mobs)];
133
+			$unit = new Daemon_Combat_Unit();
134
+			$unit->attachDbClient($this->dbClient);
135
+			$unit->get(array('combat_unit_id' => $row['combat_unit_id']));
136
+			$unit->name = $row['name'];
137
+			$unit->faction_id = $factionId;
138
+			$unit->_id = "mob_$i";
139
+			$result[$unit->_id] = $unit;
140
+		}
141
+		return $result;
142
+	}
143
+
144
+
145
+	protected function getWinnerFaction(array $units)
146
+	{
147
+		$survivors = array();
148
+		foreach($units as $factionId => $faction)
149
+		{
150
+			$survivors[$factionId] = 0;
151
+			foreach($faction as $unit)
152
+			{
153
+				if($unit->health > 0)
154
+					$survivors[$unit->faction_id] += 1;
155
+			}
156
+		}
157
+		$winnerId = null;
158
+		$winnerCount = 0;
159
+		foreach($survivors as $factionId => $count)
160
+		{
161
+			if($winnerCount < $count)
162
+			{
163
+				$winnerId = $factionId;
164
+				$winnerCount = $count;
165
+			}
166
+		}
167
+		return $winnerId;
168
+	}
169
+
170
+
171
+	//kicks enemies out of the caern
172
+	public function kickEnemies($locationId, $factionId)
173
+	{
174
+		$sql = "UPDATE character_data SET location_id=NULL WHERE location_id=:locationId";
175
+		$params = array('locationId' => $locationId);
176
+		if($factionId)
177
+		{
178
+			$sql .= " AND faction_id!=:factionId";
179
+			$params['factionId'] = $factionId;
180
+		}
181
+		$this->dbClient->query($sql, $params);
182
+	}
183
+
184
+
185
+	//kicks neutral characters out of the caern
186
+	protected function kickNeutrals($locationId)
187
+	{
188
+		$sql = "UPDATE character_data SET location_id=NULL WHERE location_id=:id AND faction_id IS NULL";
189
+		$this->dbClient->query($sql, array('id' => $locationId));
190
+	}
191
+
192
+
193
+	protected function putCharacterUnits(array $units)
194
+	{
195
+		$sqlLive = "UPDATE character_data SET health=:hp WHERE character_id=:id";
196
+		$sqlDie = "UPDATE character_data SET health=0, location_id=null WHERE character_id=:id";
197
+		foreach($units as $faction)
198
+		{
199
+			foreach($faction as $unit)
200
+			{
201
+				if(!is_numeric($unit->_id))
202
+					continue;//monsters have string IDs
203
+				if($unit->health > 0)
204
+				{
205
+					$params = array('id' => $unit->_id, 'hp' => $unit->health);
206
+					$this->dbClient->query($sqlLive, $params);
207
+				}
208
+				else
209
+				{
210
+					$params = array('id' => $unit->_id);
211
+					$this->dbClient->query($sqlDie, $params);
212
+				}
213
+			}
214
+		}
215
+	}
216
+
217
+
218
+	public function runCombat(array $units, $locationFactionId)
219
+	{
220
+		$combat = new Daemon_Combat();
221
+		$logger = new Daemon_Combat_Log();
222
+		$combat->attachLogger($logger);
223
+		foreach($units as $factionId => $faction)
224
+		{
225
+			foreach($faction as $unit)
226
+			{
227
+				$attacker = ($factionId != $locationFactionId);
228
+				$combat->addUnit($unit->_id, $unit, $attacker);
229
+			}
230
+		}
231
+		$combat->execute();
232
+		foreach($combat->units as &$unit)
233
+			$unit->health = max(0, ceil($unit->health));
234
+		//prepare summary
235
+		$summary = array();
236
+		$summary[] = '<table class="border">';
237
+		$summary[] = '<caption>Podsumowanie bitwy</caption>';
238
+		$summary[] = '<tr>';
239
+		$summary[] = '<th rowspan="2">Strona</th><th colspan="3">Postać</th>';
240
+		$summary[] = '<th colspan="3">Wykonane ataki</th><th colspan="3">Otrzymane ataki</th>';
241
+		$summary[] = '</tr>';
242
+		$summary[] = '<tr>';
243
+		$summary[] = '<th>Imię</th><th>Frakcja</th><th>Zdrowie</th>';
244
+		$summary[] = '<th>Ataki</th><th>Obrażenia</th><th>Średnia</th>';
245
+		$summary[] = '<th>Ataki</th><th>Obrażenia</th><th>Średnia</th>';
246
+		$summary[] = '</tr>';
247
+		foreach($units as $factionId => $faction)
248
+		{
249
+			foreach($faction as $unit)
250
+			{
251
+				$attackerTxt = $unit->_attacker ? 'atak' : 'obrona';
252
+				$dmgTotal = $unit->_dmgDealt + $unit->_dmgTaken;
253
+				$health = max(0, ceil($unit->health_max - $unit->_dmgTaken));
254
+				$healthPercent = $unit->health_max ? round(100 * $health / $unit->health_max, 2) : 0;
255
+				$avgDealt = $unit->_cntDealt ? $unit->_dmgDealt / $unit->_cntDealt : null;
256
+				$avgTaken = $unit->_cntTaken ? $unit->_dmgTaken / $unit->_cntTaken : null;
257
+				$summary[] = '<tr>';
258
+				$summary[] = sprintf('<td>%s</td><td>%s</td><td>%s</td><td>%.2f%%</td>',
259
+					$attackerTxt, $unit->name, $unit->faction_id, $healthPercent);
260
+				$summary[] = sprintf('<td>%d</td><td>%.3f</td><td>%.3f</td>',
261
+					$unit->_cntDealt, $unit->_dmgDealt, $avgDealt);
262
+				$summary[] = sprintf('<td>%d</td><td>%.3f</td><td>%.3f</td>',
263
+					$unit->_cntTaken, $unit->_dmgTaken, $avgTaken);
264
+				$summary[] = '</tr>';
265
+			}
266
+		}
267
+		$summary[] = '</table>';
268
+		return implode('', $summary) . (string) $logger;
269
+	}
270
+}
... ...
@@ -0,0 +1,164 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//combat engine
4
+//usage:
5
+//$combat = new Daemon_Combat();
6
+//$logger = new Daemon_Combat_Log();
7
+//$combat->attachLogger($logger);
8
+//$combat->addUnit('a', $unit1, true);
9
+//$combat->addUnit('b', $unit2, false);
10
+//$combat->execute();
11
+//$combatLog = (string) $logger;
12
+class Daemon_Combat
13
+{
14
+	public $units = array();
15
+	public $sideA = array();
16
+	public $sideB = array();
17
+	public $round = 0;
18
+	public $roundLimit = 120;
19
+	public $tickLimit = 1;
20
+	private $_logger = null;
21
+
22
+
23
+	public function addUnit($unitId, Daemon_Combat_Unit $unit, $attacker)
24
+	{
25
+		$unit->attachLogger($this->_logger);
26
+		$unit->_id = $unitId;
27
+		$unit->_ticks = 0;
28
+		$unit->_initiative = mt_rand(0, 32 * $unit->speed);
29
+		$unit->_target = null;
30
+		$unit->_threat = array();
31
+		$unit->_dmgDealt = 0;
32
+		$unit->_dmgTaken = 0;
33
+		$this->units[$unitId] = $unit;
34
+		$unit->_attacker = (bool) $attacker;
35
+		if($unit->_attacker)
36
+		{
37
+			$this->sideA[$unitId] = $unit;
38
+			$unit->_allies = &$this->sideA;
39
+			$unit->_enemies = &$this->sideB;
40
+		}
41
+		else
42
+		{
43
+			$this->sideB[$unitId] = $unit;
44
+			$unit->_allies = &$this->sideB;
45
+			$unit->_enemies = &$this->sideA;
46
+		}
47
+	}
48
+
49
+
50
+	public function attachLogger(Daemon_Combat_Log $logger)
51
+	{
52
+		$this->_logger = $logger;
53
+		foreach($this->units as $unit)
54
+			$unit->attachLogger($logger);
55
+	}
56
+
57
+
58
+	protected function callbackActive($unit)
59
+	{
60
+		return $unit->_ticks > $this->tickLimit;
61
+	}
62
+
63
+
64
+	protected function callbackSpeedSum($prev, $unit)
65
+	{
66
+		return $prev + $unit->speed;
67
+	}
68
+
69
+
70
+	protected function callbackTickCompare($unit1, $unit2)
71
+	{
72
+		if($unit1->_ticks == $unit2->_ticks)
73
+			return $unit1->_initiative - $unit2->_initiative;
74
+		else return ($unit1->_ticks < $unit2->_ticks) ? -1 : +1;
75
+	}
76
+
77
+
78
+	protected function callbackTickInc($unit, $key)
79
+	{
80
+		if($unit->health > 0)
81
+			$unit->_ticks += $unit->speed;
82
+		else $unit->_ticks = null;
83
+	}
84
+
85
+
86
+	protected function debug($round, array $units)
87
+	{
88
+		if($this->_logger)
89
+		{
90
+			$result = array("Segment $round");
91
+			foreach($units as $unit)
92
+				$result[] = (string) $unit;
93
+			$this->_logger->add($result);
94
+		}
95
+	}
96
+
97
+
98
+	public function execute($noCleanup = false)
99
+	{
100
+		if(!$this->units)
101
+			return;
102
+		$unitCount = count($this->units);
103
+		if ($this->_logger)
104
+			$this->_logger->groupCombat = ($unitCount > 2);
105
+		$round = 0;
106
+		$roundLimit = 100 + 10 * $unitCount;
107
+		$speedSum = array_reduce($this->units, array($this, 'callbackSpeedSum'), 0);
108
+		$this->tickLimit = 1 + ceil(2 * $speedSum / count($this->units));
109
+		while($round < $roundLimit)
110
+		{
111
+			$victory = false;
112
+			array_walk($this->units, array($this, 'callbackTickInc'));
113
+			while($actor = $this->getActiveUnit())
114
+			{
115
+				++$round;
116
+				$actor->_ticks -= $this->tickLimit;
117
+				$actor->executeRound($round);
118
+				//victory check, sides should be updated by units
119
+				$victory = (!count($this->sideA) || !count($this->sideB));
120
+				if($victory)
121
+					break;
122
+			}
123
+			if($victory)
124
+				break;
125
+		}
126
+		//after-combat regen & fixes
127
+		if (!$noCleanup)
128
+		{
129
+			foreach($this->units as $unit)
130
+			{
131
+				if ($unit->health < 1)
132
+				{
133
+					$unit->health = 0;
134
+				}
135
+				elseif (($unit->regen > 0) && ($unit->health < $unit->health_max))
136
+				{
137
+					$unit->health = $unit->health_max;
138
+					if($this->_logger)
139
+						$this->_logger->add("<i>$unit->name</i> regeneruje pełnię zdrowia.<br>");
140
+				}
141
+				else
142
+				{
143
+					$unit->health = Daemon_Math::round($unit->health);
144
+				}
145
+			}
146
+		}
147
+	}
148
+
149
+
150
+	protected function getActiveUnit()
151
+	{
152
+		$active = array_filter($this->units, array($this, 'callbackActive'));
153
+		uasort($active, array($this, 'callbackTickCompare'));
154
+		return array_pop($active);
155
+	}
156
+
157
+
158
+	public function reset()
159
+	{
160
+		$this->units = array();
161
+		$this->sideA = array();
162
+		$this->sideB = array();
163
+	}
164
+}
... ...
@@ -0,0 +1,117 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+class Daemon_Combat_Log
4
+{
5
+	private $buffer;
6
+	public $groupCombat;
7
+
8
+
9
+	public function __construct()
10
+	{
11
+		$this->clear();
12
+	}
13
+
14
+
15
+	public function __toString()
16
+	{
17
+		return $this->buffer;
18
+	}
19
+
20
+
21
+	public function add($text)
22
+	{
23
+		$this->buffer .= "$text\n";
24
+	}
25
+
26
+
27
+	public function clear()
28
+	{
29
+		$this->buffer = '';
30
+	}
31
+
32
+
33
+	public static function escape($str)
34
+	{
35
+		return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
36
+	}
37
+
38
+
39
+	public function txtAttackHit($name, $dmg, $magical, $critical, $poison, $shockDmg, $stun, $vampRegen)
40
+	{
41
+		$txt = $critical ? 'Trafienie krytyczne! ' : '';
42
+		$txt .= sprintf('<i>%s</i> zadaje <b>%.2f</b> obrażeń %s', self::escape($name),
43
+			$dmg, $magical ? 'magicznych' : 'fizycznych');
44
+		if($poison)
45
+			$txt .= ', zatruwając cel';
46
+		if($stun)
47
+			$txt .= ', ogłuszając cel';
48
+		if($vampRegen)
49
+			$txt .= sprintf(', regenerując <b>%.2f</b> HP', $vampRegen);
50
+		if($shockDmg)
51
+			$txt .= sprintf('. Otrzymuje <b>%.2f</b> obrażeń od porażenia', $shockDmg);
52
+		$txt .= '.<br>';
53
+		$this->add($txt);
54
+	}
55
+
56
+
57
+	public function txtAttackMiss($name, $magical)
58
+	{
59
+		if($magical)
60
+			$this->add('Cel odbił zaklęcie.<br>');
61
+		else $this->add(sprintf('<i>%s</i> chybił.<br>', self::escape($name)));
62
+	}
63
+
64
+
65
+	public function txtDeath($name, $flawlessName = null)
66
+	{
67
+		$txt = sprintf('<i>%s</i> umiera.<br>', $name);
68
+		if($flawlessName)
69
+		{
70
+			$atxt = array('<i>%s</i> ziewa.', '<i>%s</i> śmieje się szyderczo.');
71
+			$txt .= sprintf($atxt[array_rand($atxt)], self::escape($flawlessName)).'<br>';
72
+		}
73
+		$this->add($txt);
74
+	}
75
+
76
+
77
+	public function txtDemon($regen)
78
+	{
79
+		$this->add(sprintf('Demon w zbroi wchłonął zaklęcie. Cel regeneruje %.2f obrażeń.<br>', $regen));
80
+	}
81
+
82
+
83
+	public function txtPoison($name, $dmg)
84
+	{
85
+		$this->add(sprintf('<i>%s</i> otrzymuje %.2f obrażeń od trucizny.<br>', self::escape($name), $dmg));
86
+	}
87
+
88
+
89
+	public function txtRegen($name, $regen)
90
+	{
91
+		$this->add(sprintf('<i>%s</i> regeneruje %.2f obrażeń.<br>', self::escape($name), $regen));
92
+	}
93
+
94
+
95
+	public function txtRoundFooter()
96
+	{
97
+		$this->add('</p>');
98
+	}
99
+
100
+
101
+	public function txtRoundHeader($round)
102
+	{
103
+		$this->add(sprintf('<h3>Akcja %d</h3><p>', $round));
104
+	}
105
+
106
+
107
+	public function txtTargetHeader($actorName, $targetName)
108
+	{
109
+		if($targetName)
110
+		{
111
+			if ($this->groupCombat)
112
+				$this->add(sprintf('<i>%s</i> wybiera cel: <i>%s</i>.<br>',
113
+					self::escape($actorName), self::escape($targetName)));
114
+		}
115
+		else $this->add(sprintf('<i>%s</i> nie ma już przeciwników.<br>', self::escape($actorName)));
116
+	}
117
+}
... ...
@@ -0,0 +1,261 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//combat stats (daemon or monster)
4
+class Daemon_Combat_Unit extends Daemon_DbObject_CombatUnit
5
+{
6
+	private $_logger = null;
7
+	//combat variables
8
+	public $_id = null;
9
+	public $_flawless = true;
10
+	public $_ticks = 0;
11
+	public $_initiative = 0;
12
+	public $_poison = 0;
13
+	public $_attacker = null; //attacker or defender
14
+	public $_target = null; //current target
15
+	public $_threat = array(); //current threat
16
+	public $_allies = array(); //global sideA/sideB list
17
+	public $_enemies = array(); //global sideB/sideA list
18
+	public $_cntDealt = 0;
19
+	public $_dmgDealt = 0;
20
+	public $_cntTaken = 0;
21
+	public $_dmgTaken = 0;
22
+
23
+
24
+	public function __toString()
25
+	{
26
+		return "[id: $this->_id, name: $this->name, ticks: $this->_ticks, health: $this->health/$this->health_max]";
27
+	}
28
+
29
+
30
+	public function attachLogger(Daemon_Combat_Log $logger = null)
31
+	{
32
+		$this->_logger = $logger;
33
+	}
34
+
35
+
36
+	protected function calculateAttack(array $params)
37
+	{
38
+		//attack count - check for swarm
39
+		if(self::SP_SWARM == $params['sp_type'])
40
+			$attackCount = ceil($params['count'] * $this->health / $this->health_max);
41
+		else $attackCount = $params['count'];
42
+		//prepare variables
43
+		$magical = ('m' == $params['type']);
44
+		if($magical)
45
+		{
46
+			$targetDef = $this->_target->mdef;
47
+			$targetRes = $this->_target->mres;
48
+			$armor = 0;
49
+		}
50
+		else
51
+		{
52
+			$targetDef = $this->_target->pdef;
53
+			$targetRes = $this->_target->pres;
54
+			$armor = $this->_target->armor;
55
+			if(self::SP_ETHER == $params['sp_type'])
56
+				$armor *= 1 - $params['sp_param']/100;
57
+		}
58
+		//calculate basic data
59
+		$toHit = 100 * $params['atk'] / ($params['atk'] + $targetDef);
60
+		$baseDmg = $params['str'] * $params['str'] / ($params['str'] + $targetRes);
61
+		//faction effects
62
+		if($this->faction_id && $this->_target->faction_id && ($this->faction_id != $this->_target->faction_id))
63
+		{
64
+			if(self::SP_FACTION == $params['sp_type'])
65
+				$baseDmg *= 1 + $params['sp_param']/100;
66
+			if(self::SP_FACTION == $this->_target->armor_sp_type)
67
+				$baseDmg /= 1 + $this->_target->armor_sp_param/100;
68
+		}
69
+		//execute attacks
70
+		for($i=0; $i < $attackCount; $i++)
71
+		{
72
+			$d100 = mt_rand(0,99);
73
+			//check for demon
74
+			$demon = $magical && (self::SP_DEMON == $this->_target->armor_sp_type);
75
+			if($demon && mt_rand(0,99) < $this->_target->armor_sp_param)
76
+			{
77
+				$regen = min($this->_target->health_max - $this->_target->health, $baseDmg * $this->_target->armor_sp_param / 100);
78
+				$this->_target->health += $regen;
79
+				if($this->_logger)
80
+					$this->_logger->txtDemon($regen);
81
+			}
82
+			//check for hit
83
+			elseif($d100 < $toHit)
84
+			{
85
+				//calculate damage
86
+				$critical = $d100>45;
87
+				if($critical)
88
+				{
89
+					$dmgMult = 2;
90
+					if(self::SP_BLOODY == $params['sp_type'])
91
+						$dmgMult += $params['sp_param']/100;
92
+				}
93
+				else $dmgMult = 1 + mt_rand(0,127)/256;
94
+				$dmg = max(0, $baseDmg * $dmgMult - $armor);
95
+				$this->_target->health -= $dmg;
96
+				//update statistics
97
+				$this->_target->_flawless = false;
98
+				$this->_target->_cntTaken += 1;
99
+				$this->_target->_dmgTaken += $dmg;
100
+				$this->_cntDealt += 1;
101
+				$this->_dmgDealt += $dmg;
102
+				//check for poison
103
+				if(self::SP_POISON == $params['sp_type'])
104
+				{
105
+					$poison = $dmg>0 ? $params['sp_param'] : 0;
106
+					if(self::SP_ANTIPOISON == $this->_target->armor_sp_type)
107
+						$poison *= 1 - $this->_target->armor_sp_param / 100;
108
+					$this->_target->_poison += $poison;
109
+				}
110
+				else $poison = 0;
111
+				//check for shock
112
+				if(self::SP_SHOCK == $this->_target->armor_sp_type)
113
+				{
114
+					$shockDmg = $dmg * $this->_target->armor_sp_param / 100;
115
+					$this->health -= $shockDmg;
116
+				}
117
+				else $shockDmg = 0;
118
+				//check for stun
119
+				$stun = $critical && (self::SP_STUN == $params['sp_type']);
120
+				if($stun)
121
+					$this->_target->_ticks = 0;
122
+				//check for vampirism
123
+				if(self::SP_VAMPIRE == $params['sp_type'])
124
+				{
125
+					$vampRegen = min($this->health_max - $this->health, $dmg * $params['sp_param']/100);
126
+					if(self::SP_ANTIVAMP == $this->_target->armor_sp_type)
127
+						$vampRegen *= 1 - $this->_target->armor_sp_param / 100;
128
+					$this->health += $vampRegen;
129
+				}
130
+				else $vampRegen = 0;
131
+				//print info
132
+				if($this->_logger)
133
+				{
134
+					$this->_logger->txtAttackHit($this->name, $dmg, $magical, $critical,
135
+						$poison, $shockDmg, $stun, $vampRegen);
136
+				}
137
+			}
138
+			else
139
+			{
140
+				if($this->_logger)
141
+					$this->_logger->txtAttackMiss($this->name, $magical);
142
+			}
143
+		}
144
+	}
145
+
146
+
147
+	protected function executeAttacks()
148
+	{
149
+		if(!$this->_target)
150
+			return;
151
+		$this->_target->_threat[$this->_id] = $this;
152
+		if($this->count1 && $this->type1)
153
+		{
154
+			$attackParams = array(
155
+				'str' => $this->str1,
156
+				'atk' => $this->atk1,
157
+				'type' => $this->type1,
158
+				'count' => $this->count1,
159
+				'sp_type' => $this->sp1_type,
160
+				'sp_param' => $this->sp1_param,
161
+			);
162
+			$this->calculateAttack($attackParams);
163
+		}
164
+		if($this->count2 && $this->type2)
165
+		{
166
+			$attackParams = array(
167
+				'str' => $this->str2,
168
+				'atk' => $this->atk2,
169
+				'type' => $this->type2,
170
+				'count' => $this->count2,
171
+				'sp_type' => $this->sp2_type,
172
+				'sp_param' => $this->sp2_param,
173
+			);
174
+			$this->calculateAttack($attackParams);
175
+		}
176
+		//check for target death
177
+		if($this->_target->health <= 0)
178
+		{
179
+			$this->_target->_ticks = null;
180
+			unset($this->_threat[$this->_target->_id]);
181
+			unset($this->_enemies[$this->_target->_id]);
182
+			if($this->_logger)
183
+				$this->_logger->txtDeath($this->_target->name, $this->_flawless ? $this->name : null);
184
+		}
185
+	}
186
+
187
+
188
+	public function executeRound($round)
189
+	{
190
+		if($this->_logger)
191
+			$this->_logger->txtRoundHeader($round);
192
+		$this->regen();
193
+		$this->pickTarget();
194
+		if($this->_target)
195
+			$this->executeAttacks();
196
+		$this->poison();
197
+		if($this->_logger)
198
+			$this->_logger->txtRoundFooter();
199
+	}
200
+
201
+
202
+	protected function pickTarget()
203
+	{
204
+		//clear old target
205
+		$this->_target = null;
206
+		//try current threat
207
+		while($this->_threat && !$this->_target)
208
+		{
209
+			$targetId = array_rand($this->_threat);
210
+			$this->_target = $this->_threat[$targetId];
211
+			if($this->_target->health <= 0) //killed by someone else
212
+			{
213
+				$this->_target = null;
214
+				unset($this->_threat[$targetId]);
215
+			}
216
+		}
217
+		//no threat, try other enemies
218
+		if($this->_enemies && !$this->_target)
219
+		{
220
+			$targetId = array_rand($this->_enemies);
221
+			$this->_target = $this->_enemies[$targetId];
222
+		}
223
+		//print message
224
+		if($this->_logger)
225
+		{
226
+			if($this->_target)
227
+				$this->_logger->txtTargetHeader($this->name, $this->_target->name);
228
+			else $this->_logger->txtTargetHeader($this->name, null);
229
+		}
230
+	}
231
+
232
+
233
+	protected function poison()
234
+	{
235
+		if($this->_poison)
236
+		{
237
+			$dmg = $this->_poison * $this->_poison / ($this->_poison + $this->pres);
238
+			$this->health -= $dmg;
239
+			if($this->_logger)
240
+				$this->_logger->txtPoison($this->name, $dmg);
241
+			if($this->health <= 0)
242
+			{
243
+				unset($this->_allies[$this->_id]);
244
+				if($this->_logger)
245
+					$this->_logger->txtDeath($this->name, false);
246
+			}
247
+		}
248
+	}
249
+
250
+
251
+	protected function regen()
252
+	{
253
+		if($this->regen && $this->health < $this->health_max)
254
+		{
255
+			$delta = min($this->health_max - $this->health, $this->regen);
256
+			$this->health += $delta;
257
+			if($this->_logger)
258
+				$this->_logger->txtRegen($this->name, $delta);
259
+		}
260
+	}
261
+}
... ...
@@ -0,0 +1,56 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+class Daemon_Config
4
+{
5
+	public $applicationRoot;
6
+	public $applicationTitle = 'Daemon 2';
7
+	public $applicationUrl;
8
+	public $applicationMail;
9
+	public $dbHost;
10
+	public $dbSchema;
11
+	public $dbUser;
12
+	public $dbPassword;
13
+	public $dbPrefix;
14
+	public $minifyHtml = false;
15
+	public $sessionName = 'sessid';
16
+	public $tsDelta = 0.5;
17
+
18
+
19
+	public function __construct($applicationRoot = null)
20
+	{
21
+		$this->applicationRoot = (string) $applicationRoot;
22
+		$hostname = mb_strtolower(getenv('SERVER_NAME'));
23
+		if(!$hostname)
24
+			$hostname = '_cron';
25
+		$fileName = $hostname . '.php';
26
+		$this->loadFile($this->getFilePath('cfg', $fileName));
27
+	}
28
+
29
+
30
+	//loads config from file
31
+	public function loadFile($path)
32
+	{
33
+		if(is_readable($path))
34
+		{
35
+			$data = (array) include $path;
36
+			foreach($data as $key=>$value)
37
+				$this->$key = $value;
38
+		}
39
+	}
40
+
41
+
42
+	//implodes parameters into relative path and prepends it with root
43
+	public function getFilePath(/*...*/)
44
+	{
45
+		$aPath = array_filter(func_get_args());
46
+		array_unshift($aPath, $this->applicationRoot);
47
+		return implode(DIRECTORY_SEPARATOR, $aPath);
48
+	}
49
+
50
+
51
+	//generates URL from relative path
52
+	public function getUrl($path)
53
+	{
54
+		return $path ? "$this->applicationUrl$path" : $this->applicationUrl;
55
+	}
56
+}
... ...
@@ -0,0 +1,158 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//prototype for page controller
4
+class Daemon_Controller
5
+{
6
+	//global objects
7
+	protected $cfg = null;
8
+	protected $dbClient = null;
9
+	protected $dbCfg = null;
10
+	protected $activeCharacter = null;
11
+	protected $characterData = null;
12
+	protected $location = null;
13
+	protected $player = null;
14
+	protected $view = null;
15
+	//execution parameters
16
+	protected $disableMessages = false;
17
+	protected $disablePlayer = false;
18
+	protected $requireActiveChar = false;
19
+	protected $requireAuthentication = false;
20
+	protected $requireFactionMatch = false;
21
+	protected $requireLocation = false;
22
+	protected $requireNoEvents = false;
23
+	protected $requiredRole = null;
24
+	//output parameters
25
+	protected $pageOutputMode = Daemon_View::MODE_HTML;
26
+	protected $pageSubtitle = null;
27
+	protected $pageSubtitleDetails = null;
28
+	protected $pageSubtitleUseQuery = false;
29
+	protected $pageTemplatePath;
30
+
31
+
32
+	final public function __construct(Daemon_Config $cfg)
33
+	{
34
+		$this->cfg = $cfg;
35
+		session_name($this->cfg->sessionName);
36
+		session_cache_limiter(null);
37
+		session_start();
38
+		$this->dbClient = Daemon::createDbClient($this->cfg);
39
+		$this->dbCfg = new Daemon_DbConfig($this->dbClient);
40
+		if(!$this->disablePlayer)
41
+		{
42
+			$this->player = new Daemon_DbObject_Player($this->dbClient);
43
+			$this->activeCharacter = $this->player->getActiveCharacter();
44
+			$this->activeCharacter->updateLastAction();
45
+			$this->characterData = $this->activeCharacter->getCharacterData();
46
+			$this->location = $this->characterData->getLocation();
47
+		}
48
+		$this->view = new Daemon_View($this->cfg);
49
+	}
50
+
51
+
52
+	//checks last action's timestamp
53
+	final private function checkActionTimestamp()
54
+	{
55
+		$lastAction = isset($_SESSION['ts']) ? $_SESSION['ts'] : 0.0;
56
+		$_SESSION['ts'] = microtime(true);
57
+		return (bool) ($_SESSION['ts'] >= $lastAction + $this->cfg->tsDelta);
58
+	}
59
+
60
+
61
+	final public function execute()
62
+	{
63
+		//prepare controller
64
+		$this->prepareModel();
65
+		$this->validatePlayer();
66
+		//check last action's timestamp
67
+		if($_POST && !$this->checkActionTimestamp())
68
+		{
69
+			Daemon_MsgQueue::add('Operacja anulowana: za duża częstość.');
70
+			$_POST = array();
71
+		}
72
+		//execute commands
73
+		$cmdExecuted = (bool) $this->runCommands();
74
+		//display page
75
+		$this->prepareView();
76
+		if($this->pageSubtitleUseQuery)
77
+		{
78
+			if($qs = getenv('QUERY_STRING'))
79
+				$this->pageSubtitleDetails = urldecode($qs);
80
+		}
81
+		$this->view->setPageTitle($this->pageSubtitle, $this->pageSubtitleDetails, $cmdExecuted);
82
+		if(!$this->disablePlayer)
83
+		{
84
+			$this->view->setGameHeader($this->player->getPlayerId(),
85
+				$this->activeCharacter, $this->characterData, $this->location);
86
+			$this->view->setPageSkin($this->player->skin);
87
+		}
88
+		else $this->view->setPageSkin(null);
89
+		if(!$this->disableMessages)
90
+			$messages = Daemon_MsgQueue::getAll();
91
+		else $messages = array();
92
+		if($this->dbCfg->globalMessage)
93
+			$messages[] = $this->dbCfg->globalMessage;
94
+		$this->view->setMessages($messages);
95
+		$this->view->display($this->pageTemplatePath, $this->pageOutputMode);
96
+	}
97
+
98
+
99
+	//page-specific
100
+	protected function prepareModel()
101
+	{
102
+	}
103
+
104
+
105
+	//page-specific
106
+	protected function prepareView()
107
+	{
108
+	}
109
+
110
+
111
+	//page-specific
112
+	protected function runCommands()
113
+	{
114
+		return false;
115
+	}
116
+
117
+
118
+	final private function validatePlayer()
119
+	{
120
+		if($this->disablePlayer)
121
+			return;
122
+		if($this->requireAuthentication && !$this->player->getPlayerId())
123
+		{
124
+			Daemon_MsgQueue::add('Strona dostępna tylko dla zalogowanych użytkowników.');
125
+			Daemon::redirect($this->cfg->getUrl(null));
126
+			exit;
127
+		}
128
+		if($this->requireActiveChar && !$this->player->getCharacterId())
129
+		{
130
+			Daemon_MsgQueue::add('Musisz najpierw wybrać aktywną postać.');
131
+			Daemon::redirect($this->cfg->getUrl('account'));
132
+			exit;
133
+		}
134
+		if($this->requiredRole && !$this->player->hasRole($this->requiredRole))
135
+		{
136
+			Daemon_MsgQueue::add('Nie masz uprawnień do korzystania z tej funkcji.');
137
+			Daemon::redirect($this->cfg->getUrl('account'));
138
+			exit;
139
+		}
140
+		if($this->requireLocation && !$this->location->location_id)
141
+		{
142
+			Daemon::redirect($this->cfg->getUrl('respawn'));
143
+			exit;
144
+		}
145
+		if($this->requireNoEvents && $this->characterData->getLocationEvent())
146
+		{
147
+			Daemon::redirect($this->cfg->getUrl('map'));
148
+			exit;
149
+		}
150
+		if($this->requireFactionMatch && $this->location->faction_id && $this->characterData->faction_id
151
+			&& ($this->location->faction_id != $this->characterData->faction_id))
152
+		{
153
+			Daemon_MsgQueue::add('Odejdź! Nie przyjmujemy takich jak ty!');
154
+			Daemon::redirect($this->cfg->getUrl('map'));
155
+			exit;
156
+		}
157
+	}
158
+}
... ...
@@ -0,0 +1,148 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//abstraction for standard db queries
4
+class Daemon_DbClient
5
+{
6
+	protected $dbh;
7
+
8
+
9
+	public function __construct(PDO $dbHandle)
10
+	{
11
+		$this->dbh = $dbHandle;
12
+	}
13
+
14
+
15
+	//reads maximum length of columns of selected table
16
+	public function getColumnMaxLength($table, $column)
17
+	{
18
+		$sql = 'SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS
19
+			WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = :table AND COLUMN_NAME = :column';
20
+		$params = array('table' => $table, 'column' => $column);
21
+		return $this->selectColumn($sql, $params);
22
+	}
23
+
24
+
25
+	//returns internal PDO handle
26
+	public function getDbHandle()
27
+	{
28
+		return $this->dbh;
29
+	}
30
+
31
+
32
+	//internal exception handler
33
+	private function exceptionHandler(PDOException $e, $duplicateMsg = null)
34
+	{
35
+		//prepare params
36
+		$sqlstate = $e->getCode();
37
+		$dbMessage = $e->getMessage();
38
+		//check error type
39
+		if('23000' == $sqlstate && false !== stripos($dbMessage, 'duplicate'))
40
+		{
41
+			$message = $duplicateMsg ? $duplicateMsg : 'Wybrany obiekt już istnieje.';
42
+			Daemon_MsgQueue::add($message);
43
+		}
44
+		else throw $e;
45
+	}
46
+
47
+
48
+	//executes a query, returns the statement resource
49
+	public function execute($sql, array $params = array(), $duplicateMsg = null)
50
+	{
51
+		try
52
+		{
53
+			$sth = $this->dbh->prepare($sql);
54
+			foreach((array)$params as $name => $value)
55
+				$sth->bindValue(':'.$name, $value, self::paramType($value));
56
+			$sth->execute();
57
+			return $sth;
58
+		}
59
+		catch(PDOException $e)
60
+		{
61
+			$this->exceptionHandler($e, $duplicateMsg);
62
+			return null;
63
+		}
64
+	}
65
+
66
+
67
+	//returns ID of last inserted row
68
+	public function lastInsertId()
69
+	{
70
+		return $this->dbh->lastInsertId();
71
+	}
72
+
73
+
74
+	//returns appriopriate PDO::PARAM_X constant
75
+	public static function paramType($value)
76
+	{
77
+		if(is_null($value))
78
+			return PDO::PARAM_NULL;
79
+		elseif(is_int($value))
80
+			return PDO::PARAM_INT;
81
+		else return PDO::PARAM_STR;
82
+	}
83
+
84
+
85
+	//executes a generic non-select query
86
+	public function query($sql, array $params = array(), $duplicateMsg = null)
87
+	{
88
+		$sth = $this->execute($sql, $params, $duplicateMsg);
89
+		return !is_null($sth);
90
+	}
91
+
92
+
93
+	//quotes value for safe use in query
94
+	public function quote($value)
95
+	{
96
+		return $this->dbh->quote($value, self::paramType($value));
97
+	}
98
+
99
+
100
+	//select multiple rows from table
101
+	public function selectAll($sql, array $params = array())
102
+	{
103
+		$sth = $this->execute($sql, $params);
104
+		if(is_null($sth))
105
+			return null;
106
+		return $sth->fetchAll(PDO::FETCH_ASSOC);
107
+	}
108
+
109
+
110
+	//select single column from table
111
+	public function selectColumn($sql, array $params = array())
112
+	{
113
+		$sth = $this->execute($sql, $params);
114
+		if(is_null($sth))
115
+			return null;
116
+		return $sth->fetchAll(PDO::FETCH_COLUMN, 0);
117
+	}
118
+
119
+
120
+	//select single row from table (as array)
121
+	public function selectRow($sql, array $params = array())
122
+	{
123
+		$sth = $this->execute($sql, $params);
124
+		if(is_null($sth))
125
+			return null;
126
+		return $sth->fetch(PDO::FETCH_ASSOC);
127
+	}
128
+
129
+
130
+	//select single row from table (as object)
131
+	public function selectObject($sql, array $params = array(), $className = 'stdClass')
132
+	{
133
+		$sth = $this->execute($sql, $params);
134
+		if(is_null($sth))
135
+			return null;
136
+		return $sth->fetchObject($className);
137
+	}
138
+
139
+
140
+	//select single value from table
141
+	public function selectValue($sql, array $params = array())
142
+	{
143
+		$sth = $this->execute($sql, $params);
144
+		if(is_null($sth))
145
+			return null;
146
+		return $sth->fetchColumn(0);
147
+	}
148
+}
... ...
@@ -0,0 +1,87 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//manager for game world variables
4
+class Daemon_DbConfig
5
+{
6
+	private $dbClient;
7
+	private $data;
8
+
9
+
10
+	public function __construct(Daemon_DbClient $dbClient)
11
+	{
12
+		$this->dbClient = $dbClient;
13
+	}
14
+
15
+
16
+	public function __get($name)
17
+	{
18
+		return $this->get($name);
19
+	}
20
+
21
+
22
+	public function __set($name, $value)
23
+	{
24
+		return $this->set($name, $value);
25
+	}
26
+
27
+
28
+	public function get($name)
29
+	{
30
+		if(!isset($this->data[$name]))
31
+		{
32
+			$sql = "SELECT value FROM parameters WHERE name=:name";
33
+			$params = array('name' => $name);
34
+			$this->data[$name] = $this->dbClient->selectValue($sql, $params);
35
+		}
36
+		return $this->data[$name];
37
+	}
38
+
39
+
40
+	public function set($name, $value)
41
+	{
42
+		if (empty($value))
43
+			$value = '';
44
+		$sql = "INSERT INTO parameters(name, value) VALUES (:name, :value) ON DUPLICATE KEY UPDATE value=:value";
45
+		$params = array('name' => $name, 'value' => $value);
46
+		$this->dbClient->query($sql, $params);
47
+		$this->data[$name] = $value;
48
+	}
49
+
50
+
51
+	public function getGeneratorWeights($type)
52
+	{
53
+		$keys = array(
54
+			'pstr_p', 'pstr_c', 'patk_p', 'patk_c', 'pdef_p', 'pdef_c', 'pres_p', 'pres_c',
55
+			'mstr_p', 'mstr_c', 'matk_p', 'matk_c', 'mdef_p', 'mdef_c', 'mres_p', 'mres_c',
56
+			'armor', 'speed', 'regen', 'special_param');
57
+		return $this->getGeneratorOptions("w_$type", $keys);
58
+	}
59
+
60
+
61
+	private function getGeneratorOptions($key, array $keys)
62
+	{
63
+		$result = json_decode($this->get("generator_$key"), true);
64
+		if (!is_array($result))
65
+			$result = array();
66
+		foreach ($keys as $key)
67
+		{
68
+			if (empty($result[$key]))
69
+				$result[$key] = 0;
70
+		}
71
+		return $result;
72
+	}
73
+
74
+
75
+	public function setGeneratorWeights($type, array $options)
76
+	{
77
+		$this->setGeneratorOptions("w_$type", $options);
78
+	}
79
+
80
+
81
+	private function setGeneratorOptions($key, array $options)
82
+	{
83
+		foreach ($options as &$val)
84
+			$val = floatval(str_replace(',', '.', $val));
85
+		$this->set("generator_$key", json_encode($options));
86
+	}
87
+}
... ...
@@ -0,0 +1,108 @@
1
+<?php
2
+//@author Krzysztof Sikorski
3
+//active record pattern
4
+class Daemon_DbObject
5
+{
6
+	protected $_dbClient;
7
+	protected $_tableName;
8
+	protected $_index = array();//names of index columns
9
+
10
+
11
+	public function __construct()
12
+	{
13
+		if($params = func_get_args())
14
+			$this->import($params);
15
+	}
16
+
17
+
18
+	public function attachDbClient(Daemon_DbClient $dbClient = null)
19
+	{
20
+		$this->_dbClient = $dbClient;
21
+	}
22
+
23
+
24
+	//deletes object data from database
25
+	public function delete()
26
+	{
27
+		$cond = array();
28
+		$params = array();
29
+		foreach($this->_index as $col)
30
+		{
31
+			$cond[] = "$col=:$col";
32
+			$params[$col] = $this->$col;
33
+		}
34
+		$cond = implode(' AND ', $cond);
35
+		if(!$cond)
36
+			throw new RuntimeException('Index not specified.');
37
+		$sql = "DELETE FROM $this->_tableName WHERE $cond";
38
+		$this->_dbClient->query($sql, $params);
39
+	}
40
+
41
+
42
+	//retrieves object data from database
43
+	public function get(array $params, $ignoreDuplicates = false)
44
+	{
45
+		if(!$params)
46
+			throw new RuntimeException('Params not specified.');
47
+		$cond = array();
48
+		foreach(array_keys($params) as $key)
49
+			$cond[] = "$key=:$key";
50
+		$cond = implode(' AND ', $cond);
51
+		$sql = "SELECT * FROM $this->_tableName WHERE $cond ORDER BY RAND() LIMIT 2";
52
+		$data = $this->_dbClient->selectAll($sql, $params);
53
+		if(is_array($data) && isset($data[0]))
54
+		{
55
+			if(!$ignoreDuplicates && (count($data) > 1))
56
+				throw new RuntimeException('Multiple rows found.');
57
+			foreach($data[0] as $key => $val)
58
+				$this->$key = $val;
59
+		}
60
+		return true;
61
+	}
62
+
63
+
64
+	//copies params into object data
65
+	public function import($params)
66
+	{
67
+		$keys = array_keys(get_object_vars($this));
68
+		foreach($keys as $key)
69
+		{
70
+			if(isset($params[$key]) && ($key[0] != '_'))
71
+				$this->$key = $params[$key];
72
+		}
73
+		$this->validate();
74
+	}
75
+
76
+
77
+	//stores object data in the database
78
+	public function put()
79
+	{
80
+		$this->validate();