Commits

WaveHack committed f2083c5

Rewrote most of the code (CacheableModel, srsly?).
Added DataMapper ORM.

  • Participants
  • Parent commits a68fd15

Comments (0)

Files changed (64)

 
 Stage 1
 -------
-- Exploration
-- Construction
-- Land Re-zoning
-- Population/Jobs
-- Military Training
+- Exploration (done)
+- Construction (done)
+- Land Re-zoning (done)
+- Population/Jobs (done)
+- Military Training (wip)
 
 Stage 2
 -------

File htdocs/index.php

 }
 
 // Path constants needed by CodeIgniter
+define('SELF',     'index.php');
+define('EXT',      '.php');
 define('BASEPATH', str_replace('\\', '/', (realpath($system_folder) . '/')));
 define('FCPATH',   str_replace('\\', '/', str_replace(pathinfo(__FILE__, PATHINFO_BASENAME), '', __FILE__)));
 define('APPPATH',  str_replace('\\', '/', (realpath($application_folder) . '/')));
 	}
 }
 
+require_once(APPPATH . 'third_party/datamapper/bootstrap.php');
 require_once(BASEPATH . 'core/CodeIgniter.php');

File opendominion.sql

+SET FOREIGN_KEY_CHECKS=0;
 SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+SET AUTOCOMMIT=0;
+START TRANSACTION;
 SET time_zone = "+00:00";
 
 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 /*!40101 SET NAMES utf8 */;
 
 DROP TABLE IF EXISTS `accounts`;
-CREATE TABLE `accounts` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`email` varchar(255) NOT NULL,
-	`password` char(40) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
-	`username` varchar(255) DEFAULT NULL,
-	`display_name` varchar(20) NOT NULL,
-	`active` tinyint(1) NOT NULL DEFAULT '0',
-	`activation_code` char(8) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
-	`registration_date` datetime NOT NULL,
-	`last_online` datetime DEFAULT NULL,
-	PRIMARY KEY (`id`),
-	UNIQUE KEY `email` (`email`),
-	UNIQUE KEY `display_name` (`display_name`),
-	UNIQUE KEY `username` (`username`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
+CREATE TABLE IF NOT EXISTS `accounts` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `email` varchar(255) NOT NULL,
+  `password` char(40) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+  `username` varchar(255) DEFAULT NULL,
+  `display_name` varchar(20) NOT NULL,
+  `active` tinyint(1) NOT NULL DEFAULT '0',
+  `activation_code` char(8) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+  `registration_date` datetime NOT NULL,
+  `last_online` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `email` (`email`),
+  UNIQUE KEY `display_name` (`display_name`),
+  UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `ci_sessions`;
-CREATE TABLE`ci_sessions` (
-	`session_id` char(32) NOT NULL DEFAULT '0',
-	`ip_address` varchar(16) NOT NULL DEFAULT '0',
-	`user_agent` varchar(120) NOT NULL,
-	`last_activity` int(10) unsigned NOT NULL DEFAULT '0',
-	`user_data` text,
-	PRIMARY KEY (`session_id`),
-	KEY `last_activity_idx` (`last_activity`)
+CREATE TABLE IF NOT EXISTS `ci_sessions` (
+  `session_id` char(32) NOT NULL DEFAULT '0',
+  `ip_address` varchar(16) NOT NULL DEFAULT '0',
+  `user_agent` varchar(120) NOT NULL,
+  `last_activity` int(10) unsigned NOT NULL DEFAULT '0',
+  `user_data` text,
+  PRIMARY KEY (`session_id`),
+  KEY `last_activity_idx` (`last_activity`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `dominions`;
-CREATE TABLE `dominions` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`account_id` int(1) unsigned NOT NULL,
-	`round_id` int(1) unsigned NOT NULL,
-	`realm_id` int(1) unsigned NOT NULL,
-	`race_id` int(1) unsigned NOT NULL,
-	`start_date` datetime DEFAULT NULL,
-	`name` varchar(20) NOT NULL,
-	`ruler_name` varchar(20) DEFAULT NULL,
-	`prestige` int(1) unsigned NOT NULL,
-	`peasants` int(1) unsigned NOT NULL,
-	`peasant_change_last_hour` int(1) NOT NULL DEFAULT '0',
-	`draftees` int(1) unsigned NOT NULL,
-	`draft_rate` int(1) unsigned NOT NULL,
-	`morale` decimal(13,2) unsigned NOT NULL,
-	`resource_platinum` int(1) unsigned NOT NULL,
-	`resource_food` int(1) unsigned NOT NULL,
-	`resource_lumber` int(1) unsigned NOT NULL,
-	`resource_mana` int(1) unsigned NOT NULL,
-	`resource_ore` int(1) unsigned NOT NULL,
-	`resource_gems` int(1) unsigned NOT NULL,
-	`resource_tech` int(1) unsigned NOT NULL,
-	`resource_boats` int(1) unsigned NOT NULL,
-	`improvement_science` int(1) unsigned NOT NULL,
-	`improvement_keep` int(1) unsigned NOT NULL,
-	`improvement_towers` int(1) unsigned NOT NULL,
-	`improvement_forges` int(1) unsigned NOT NULL,
-	`improvement_walls` int(1) unsigned NOT NULL,
-	`improvement_irrigation` int(1) unsigned NOT NULL,
-	`military_unit1` int(1) unsigned NOT NULL,
-	`military_unit2` int(1) unsigned NOT NULL,
-	`military_unit3` int(1) unsigned NOT NULL,
-	`military_unit4` int(1) unsigned NOT NULL,
-	`military_spies` int(1) unsigned NOT NULL,
-	`military_wizards` int(1) unsigned NOT NULL,
-	`military_archmages` int(1) unsigned NOT NULL,
-	`wizard_strength` int(1) unsigned NOT NULL,
-	`land_plain` int(1) unsigned NOT NULL,
-	`land_mountain` int(1) unsigned NOT NULL,
-	`land_swamp` int(1) unsigned NOT NULL,
-	`land_cavern` int(1) unsigned NOT NULL,
-	`land_forest` int(1) unsigned NOT NULL,
-	`land_hill` int(1) unsigned NOT NULL,
-	`land_water` int(1) unsigned NOT NULL,
-	`building_home` int(1) unsigned NOT NULL,
-	`building_alchemy` int(1) unsigned NOT NULL,
-	`building_farm` int(1) unsigned NOT NULL,
-	`building_smithy` int(1) unsigned NOT NULL,
-	`building_masonry` int(1) unsigned NOT NULL,
-	`building_ore_mine` int(1) unsigned NOT NULL,
-	`building_gryphon_nest` int(1) unsigned NOT NULL,
-	`building_tower` int(1) unsigned NOT NULL,
-	`building_wizard_guild` int(1) unsigned NOT NULL,
-	`building_temple` int(1) unsigned NOT NULL,
-	`building_diamond_mine` int(1) unsigned NOT NULL,
-	`building_school` int(1) unsigned NOT NULL,
-	`building_lumberyard` int(1) unsigned NOT NULL,
-	`building_forest_haven` int(1) unsigned NOT NULL,
-	`building_factory` int(1) unsigned NOT NULL,
-	`building_guard_tower` int(1) unsigned NOT NULL,
-	`building_shrine` int(1) unsigned NOT NULL,
-	`building_barracks` int(1) unsigned NOT NULL,
-	`building_dock` int(1) unsigned NOT NULL,
-	`daily_land` bit(1) NOT NULL DEFAULT b'0',
-	`daily_platinum` bit(1) NOT NULL DEFAULT b'0',
-	PRIMARY KEY (`id`),
-	UNIQUE KEY `account_round` (`account_id`,`round_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
+CREATE TABLE IF NOT EXISTS `dominions` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `account_id` int(1) unsigned NOT NULL,
+  `round_id` int(1) unsigned NOT NULL,
+  `realm_id` int(1) unsigned NOT NULL,
+  `race_id` int(1) unsigned NOT NULL,
+  `start_date` datetime DEFAULT NULL,
+  `name` varchar(20) NOT NULL,
+  `ruler_name` varchar(20) DEFAULT NULL,
+  `prestige` int(1) unsigned NOT NULL,
+  `peasants` int(1) unsigned NOT NULL,
+  `peasant_change_last_hour` int(1) NOT NULL,
+  `draftees` int(1) unsigned NOT NULL,
+  `draft_rate` int(1) unsigned NOT NULL,
+  `morale` decimal(13,2) unsigned NOT NULL,
+  `resource_platinum` int(1) unsigned NOT NULL,
+  `resource_food` int(1) unsigned NOT NULL,
+  `resource_lumber` int(1) unsigned NOT NULL,
+  `resource_mana` int(1) unsigned NOT NULL,
+  `resource_ore` int(1) unsigned NOT NULL,
+  `resource_gems` int(1) unsigned NOT NULL,
+  `resource_tech` int(1) unsigned NOT NULL,
+  `resource_boats` int(1) unsigned NOT NULL,
+  `improvement_science` int(1) unsigned NOT NULL,
+  `improvement_keep` int(1) unsigned NOT NULL,
+  `improvement_towers` int(1) unsigned NOT NULL,
+  `improvement_forges` int(1) unsigned NOT NULL,
+  `improvement_walls` int(1) unsigned NOT NULL,
+  `improvement_irrigation` int(1) unsigned NOT NULL,
+  `military_unit1` int(1) unsigned NOT NULL,
+  `military_unit2` int(1) unsigned NOT NULL,
+  `military_unit3` int(1) unsigned NOT NULL,
+  `military_unit4` int(1) unsigned NOT NULL,
+  `military_spies` int(1) unsigned NOT NULL,
+  `military_wizards` int(1) unsigned NOT NULL,
+  `military_archmages` int(1) unsigned NOT NULL,
+  `wizard_strength` int(1) unsigned NOT NULL,
+  `land_plain` int(1) unsigned NOT NULL,
+  `land_mountain` int(1) unsigned NOT NULL,
+  `land_swamp` int(1) unsigned NOT NULL,
+  `land_cavern` int(1) unsigned NOT NULL,
+  `land_forest` int(1) unsigned NOT NULL,
+  `land_hill` int(1) unsigned NOT NULL,
+  `land_water` int(1) unsigned NOT NULL,
+  `building_home` int(1) unsigned NOT NULL,
+  `building_alchemy` int(1) unsigned NOT NULL,
+  `building_farm` int(1) unsigned NOT NULL,
+  `building_smithy` int(1) unsigned NOT NULL,
+  `building_masonry` int(1) unsigned NOT NULL,
+  `building_ore_mine` int(1) unsigned NOT NULL,
+  `building_gryphon_nest` int(1) unsigned NOT NULL,
+  `building_tower` int(1) unsigned NOT NULL,
+  `building_wizard_guild` int(1) unsigned NOT NULL,
+  `building_temple` int(1) unsigned NOT NULL,
+  `building_diamond_mine` int(1) unsigned NOT NULL,
+  `building_school` int(1) unsigned NOT NULL,
+  `building_lumberyard` int(1) unsigned NOT NULL,
+  `building_forest_haven` int(1) unsigned NOT NULL,
+  `building_factory` int(1) unsigned NOT NULL,
+  `building_guard_tower` int(1) unsigned NOT NULL,
+  `building_shrine` int(1) unsigned NOT NULL,
+  `building_barracks` int(1) unsigned NOT NULL,
+  `building_dock` int(1) unsigned NOT NULL,
+  `daily_land` bit(1) NOT NULL DEFAULT b'0',
+  `daily_platinum` bit(1) NOT NULL DEFAULT b'0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `account_round` (`account_id`,`round_id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `queue_construction`;
-CREATE TABLE IF NOT EXISTS `queue_construction` (
-	`dominion_id` int(1) unsigned NOT NULL,
-	`building` enum('home','alchemy','farm','smithy','masonry','ore_mine','gryphon_nest','tower','wizard_guild','temple','diamond_mine','school','lumberyard','forest_haven','factory','guard_tower','shrine','barracks','dock') NOT NULL,
-	`amount` int(1) unsigned NOT NULL,
-	`hours` tinyint(1) unsigned NOT NULL,
-	PRIMARY KEY (`dominion_id`,`building`,`hours`),
-	KEY `dominion_id` (`dominion_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+DROP TABLE IF EXISTS `perks`;
+CREATE TABLE IF NOT EXISTS `perks` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `perk` varchar(50) NOT NULL,
+  `description` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `keyword` (`perk`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=17 ;
 
-DROP TABLE IF EXISTS `queue_exploration`;
-CREATE TABLE `queue_exploration` (
-	`dominion_id` int(1) unsigned NOT NULL,
-	`land_type` enum('plain','mountain','swamp','cavern','forest','hill','water') NOT NULL,
-	`amount` int(1) unsigned NOT NULL,
-	`hours` tinyint(1) unsigned NOT NULL,
-	PRIMARY KEY (`dominion_id`,`land_type`,`hours`),
-	KEY `dominion_id` (`dominion_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-DROP TABLE IF EXISTS `races`;
-CREATE TABLE `races` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`name` varchar(50) NOT NULL,
-	`alignment` enum('good','evil','neutral','other') NOT NULL,
-	`home_land_type` enum('plain','mountain','swamp','cavern','forest','hill','water') NOT NULL,
-	PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
-
-INSERT INTO `races` (`id`, `name`, `alignment`, `home_land_type`) VALUES
-(1, 'Human', 'good', 'plain'),
-(2, 'Orc', 'evil', 'forest');
-
-DROP TABLE IF EXISTS `race_bonuses`;
-CREATE TABLE `race_bonuses` (
-	`race_id` int(1) unsigned NOT NULL,
-	`racial_bonus_id` int(1) unsigned NOT NULL,
-	`value` decimal(13,1) DEFAULT NULL,
-	UNIQUE KEY `race_bonus` (`race_id`,`racial_bonus_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-INSERT INTO `race_bonuses` (`race_id`, `racial_bonus_id`, `value`) VALUES
-(1, 1, '5.0'),
-(2, 2, '20.0'),
-(2, 3, '50.0');
-
-DROP TABLE IF EXISTS `racial_bonuses`;
-CREATE TABLE `racial_bonuses` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`keyword` varchar(50) NOT NULL,
-	`description` varchar(255) NOT NULL,
-	PRIMARY KEY (`id`),
-	UNIQUE KEY `keyword` (`keyword`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
-
-INSERT INTO `racial_bonuses` (`id`, `keyword`, `description`) VALUES
+INSERT INTO `perks` (`id`, `perk`, `description`) VALUES
 (1, 'food_production', 'Food production'),
 (2, 'food_consumption', 'Food consumption'),
 (3, 'lumber_production', 'Lumber production'),
 (15, 'rezone_cost', 'Rezone cost'),
 (16, 'construction_cost', 'Construction cost');
 
+DROP TABLE IF EXISTS `perks_races`;
+CREATE TABLE IF NOT EXISTS `perks_races` (
+  `race_id` int(1) unsigned NOT NULL,
+  `perk_id` int(1) unsigned NOT NULL,
+  `value` decimal(13,1) DEFAULT NULL,
+  UNIQUE KEY `race_bonus` (`race_id`,`perk_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `perks_races` (`race_id`, `perk_id`, `value`) VALUES
+(1, 1, '5.0'),
+(2, 2, '20.0'),
+(2, 3, '50.0');
+
+DROP TABLE IF EXISTS `queue_construction`;
+CREATE TABLE IF NOT EXISTS `queue_construction` (
+  `dominion_id` int(1) unsigned NOT NULL,
+  `building` enum('home','alchemy','farm','smithy','masonry','ore_mine','gryphon_nest','tower','wizard_guild','temple','diamond_mine','school','lumberyard','forest_haven','factory','guard_tower','shrine','barracks','dock') NOT NULL,
+  `amount` int(1) unsigned NOT NULL,
+  `hours` tinyint(1) unsigned NOT NULL,
+  PRIMARY KEY (`dominion_id`,`building`,`hours`),
+  KEY `dominion_id` (`dominion_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `queue_exploration`;
+CREATE TABLE IF NOT EXISTS `queue_exploration` (
+  `dominion_id` int(1) unsigned NOT NULL,
+  `land_type` enum('plain','mountain','swamp','cavern','forest','hill','water') NOT NULL,
+  `amount` int(1) unsigned NOT NULL,
+  `hours` tinyint(1) unsigned NOT NULL,
+  PRIMARY KEY (`dominion_id`,`land_type`,`hours`),
+  KEY `dominion_id` (`dominion_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `races`;
+CREATE TABLE IF NOT EXISTS `races` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  `alignment` enum('good','evil','neutral','other') NOT NULL,
+  `home_land_type` enum('plain','mountain','swamp','cavern','forest','hill','water') NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
+
+INSERT INTO `races` (`id`, `name`, `alignment`, `home_land_type`) VALUES
+(1, 'Human', 'good', 'plain'),
+(2, 'Orc', 'evil', 'forest');
+
 DROP TABLE IF EXISTS `realms`;
-CREATE TABLE `realms` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`round_id` int(1) unsigned NOT NULL,
-	`realm` int(1) unsigned NOT NULL,
-	`name` varchar(20) DEFAULT NULL,
-	`monarch_user_id` int(1) unsigned DEFAULT NULL,
-	PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
-
-INSERT INTO `realms` (`id`, `round_id`, `realm`, `name`, `monarch_user_id`) VALUES
-(1, 1, 1, NULL, NULL),
-(2, 1, 2, NULL, NULL);
+CREATE TABLE IF NOT EXISTS `realms` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `round_id` int(1) unsigned NOT NULL,
+  `realm` int(1) unsigned NOT NULL,
+  `name` varchar(20) DEFAULT NULL,
+  `monarch_user_id` int(1) unsigned DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `rounds`;
-CREATE TABLE `rounds` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`round` int(1) NOT NULL,
-	`name` varchar(20) DEFAULT NULL,
-	`start_date` date NOT NULL,
-	`end_date` date NOT NULL,
-	PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
-
-INSERT INTO `rounds` (`id`, `round`, `name`, `start_date`, `end_date`) VALUES
-(1, 1, 'Initial', '2012-12-01', '2014-01-01');
+CREATE TABLE IF NOT EXISTS `rounds` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `round` int(1) NOT NULL,
+  `name` varchar(20) DEFAULT NULL,
+  `start_date` date NOT NULL,
+  `end_date` date NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `units`;
-CREATE TABLE `units` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`race_id` int(1) unsigned NOT NULL,
-	`slot` enum('1','2','3','4') NOT NULL,
-	`name` varchar(50) NOT NULL,
-	`cost_platinum` int(1) NOT NULL,
-	`cost_ore` int(1) unsigned NOT NULL,
-	`power_offense` decimal(13,1) unsigned NOT NULL,
-	`power_defense` decimal(13,1) unsigned NOT NULL,
-	`unit_power_id` int(1) DEFAULT NULL,
-	`unit_power_values` varchar(255) DEFAULT NULL,
-	PRIMARY KEY (`id`),
-	UNIQUE KEY `race_slot` (`race_id`,`slot`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=9 ;
+CREATE TABLE IF NOT EXISTS `units` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `race_id` int(1) unsigned NOT NULL,
+  `slot` enum('1','2','3','4') NOT NULL,
+  `name` varchar(50) NOT NULL,
+  `cost_platinum` int(1) NOT NULL,
+  `cost_ore` int(1) unsigned NOT NULL,
+  `power_offense` decimal(13,1) unsigned NOT NULL,
+  `power_defense` decimal(13,1) unsigned NOT NULL,
+  `networth` decimal(13,2) unsigned NOT NULL,
+  `unit_power_id` int(1) DEFAULT NULL,
+  `unit_power_values` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `race_slot` (`race_id`,`slot`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=9 ;
 
 INSERT INTO `units` (`id`, `race_id`, `slot`, `name`, `cost_platinum`, `cost_ore`, `power_offense`, `power_defense`, `networth`, `unit_power_id`, `unit_power_values`) VALUES
-(1, 1, '1', 'Spearmen', 275, 25, '3.0', '0.0', NULL, NULL),
-(2, 1, '2', 'Archer', 275, 10, '0.0', '3.0', NULL, NULL),
-(3, 1, '3', 'Knight', 1000, 75, '2.0', '6.0', 1, '25'),
-(4, 1, '4', 'Cavalry', 1250, 100, '6.0', '3.0', 2, '3'),
-(5, 2, '1', 'Savage', 375, 0, '4.0', '0.0', NULL, NULL),
-(6, 2, '2', 'Guard', 300, 25, '0.0', '3.0', NULL, NULL),
-(7, 2, '3', 'Voodoo Magi', 800, 0, '0.0', '3.0', 3, '1,600,2'),
-(8, 2, '4', 'Bone Breaker', 1050, 105, '7.0', '2.0', 4, '1,10,2');
+(1, 1, '1', 'Spearmen', 275, 25, '3.0', '0.0', '5.00', NULL, NULL),
+(2, 1, '2', 'Archer', 275, 10, '0.0', '3.0', '5.00', NULL, NULL),
+(3, 1, '3', 'Knight', 1000, 75, '2.0', '6.0', '11.70', 1, '25'),
+(4, 1, '4', 'Cavalry', 1250, 100, '6.0', '3.0', '12.15', 2, '3'),
+(5, 2, '1', 'Savage', 375, 0, '4.0', '0.0', '5.00', NULL, NULL),
+(6, 2, '2', 'Guard', 300, 25, '0.0', '3.0', '5.00', NULL, NULL),
+(7, 2, '3', 'Voodoo Magi', 800, 0, '0.0', '3.0', '5.40', 3, '1,600,2'),
+(8, 2, '4', 'Bone Breaker', 1050, 105, '7.0', '2.0', '11.90', 4, '1,10,2');
 
 DROP TABLE IF EXISTS `unit_powers`;
-CREATE TABLE `unit_powers` (
-	`id` int(1) unsigned NOT NULL AUTO_INCREMENT,
-	`keyword` varchar(50) NOT NULL,
-	`description` varchar(255) NOT NULL,
-	PRIMARY KEY (`id`),
-	UNIQUE KEY `keyword` (`keyword`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
+CREATE TABLE IF NOT EXISTS `unit_powers` (
+  `id` int(1) unsigned NOT NULL AUTO_INCREMENT,
+  `keyword` varchar(50) NOT NULL,
+  `description` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `keyword` (`keyword`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
 
 INSERT INTO `unit_powers` (`id`, `keyword`, `description`) VALUES
 (1, 'fewer_casualties', '%d%% fewer casualties'),
 (2, 'faster_return', 'Returns %d hours faster from battle'),
 (3, 'increase_defense_per_prestige', 'Defensive power increases by %d for every %d prestige, maximum +%d.'),
 (4, 'reduce_offense_per_guard_towers', 'Offensive power is reduced by %d for every %d%% Guard Towers, maxumum -%d.');
+SET FOREIGN_KEY_CHECKS=1;
+COMMIT;
 
 /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;

File opendominion/config/datamapper.php

+<?php
+/**
+ * OpenDominion
+ *
+ * An open source recreation of the fantasy wargame Dominion.
+ *
+ * @package OpenDominion
+ * @author  WaveHack <email@wavehack.net>
+ * @license http://www.dbad-license.org/ DBAD Public License
+ * @link    https://bitbucket.org/WaveHack/opendominion
+ */
+
+$config['prefix']                  = '';
+$config['join_prefix']             = '';
+$config['error_prefix']            = '<p>';
+$config['error_suffix']            = '</p>';
+$config['created_field']           = 'created';
+$config['updated_field']           = 'updated';
+$config['local_time']              = false;
+$config['unix_timestamp']          = false;
+$config['timestamp_format']        = '';
+$config['lang_file_format']        = 'model_${model}';
+$config['field_label_lang_format'] = '${model}_${field}';
+$config['auto_transaction']        = false;
+$config['auto_populate_has_many']  = false;
+$config['auto_populate_has_one']   = false;
+$config['all_array_uses_ids']      = false;
+$config['db_params']               = '';
+$config['production_cache']        = 'datamapper/cache';
+$config['extensions_path']         = 'datamapper';
+$config['extensions']              = array();

File opendominion/config/routes.php

  * @link    https://bitbucket.org/WaveHack/opendominion
  */
 
-$route['default_controller'] = 'index';
+$route['default_controller'] = 'home';
 
 // Sitemap
 //$route['sitemap.xml(.gz)?'] = 'xmlsitemap';
 
 // Account
-$route['login']            = 'account/login';
-$route['register']         = 'account/register';
-//$route['forgot-password']  = 'account/forgot-password';
-$route['logout']           = 'account/logout';
+$route['login']            = 'accounts/login';
+$route['register']         = 'accounts/register';
+$route['forgot-password']  = 'accounts/forgot-password';
+$route['logout']           = 'accounts/logout';

File opendominion/controllers/accounts.php

+<?php
+/**
+ * OpenDominion
+ *
+ * An open source recreation of the fantasy wargame Dominion.
+ *
+ * @package OpenDominion
+ * @author  WaveHack <email@wavehack.net>
+ * @license http://www.dbad-license.org/ DBAD Public License
+ * @link    https://bitbucket.org/WaveHack/opendominion
+ */
+/**
+ * Account controller
+ *
+ * @package OpenDominion\Controllers
+ */
+class Accounts extends FrontendController
+{
+	/**
+	 */
+	public function login()
+	{
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				if (
+					($this->input->post('username') === false)
+					|| ($this->input->post('password') === false)
+				) {
+					$message = 'Invalid username/password combination';
+					break;
+				}
+
+				// Get active round
+				$active_round = Round::get_active_round();
+
+				// Check if active round exists
+				if ($active_round === null) {
+					$message = 'No active round';
+					break;
+				}
+
+				// Get account
+				$account = new Account();
+				$account->group_start();
+					$account->where('username', $this->input->post('username'));
+					$account->or_where('email', $this->input->post('username'));
+				$account->group_end();
+				$account->where('password', sha1($this->input->post('password') . $this->config->item('password_salt')));
+				$account->get();
+
+				// Check if account exists
+				if ($account->result_count() === 0) {
+					$message = 'Invalid username/password combination';
+					break;
+				}
+
+				$session_data = array(
+					'account_id'  => $account->id,
+					'dominion_id' => null
+				);
+
+				// Get dominion in the active round
+				$dominion = new Dominion();
+				$dominion->where(array(
+					'account_id' => $account->id,
+					'round_id'   => $active_round->id
+				));
+				$dominion->get();
+
+				if ($dominion->result_count() === 1) {
+					$session_data['dominion_id'] = $dominion->id;
+				}
+
+				$this->session->set_userdata('login', $session_data);
+
+				redirect('status');
+			} while(0);
+
+			$this->set_content_vars('message', $message);
+		}
+
+		$this->parse('account/login');
+	}
+
+	/**
+	 */
+	public function register()
+	{
+		$this->load->helper('form');
+
+		$active_round = Round::get_active_round();
+
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				$this->load->library('form_validation');
+				$this->form_validation->set_error_delimiters('', '<br>');
+				$this->form_validation->set_message('is_unique', '%s is already in use.');
+
+				// Server side validation
+				$this->form_validation->set_rules(array(
+					array(
+						'field' => 'email',
+						'label' => 'Email',
+						'rules' => 'trim|required|max_length[255]|valid_email|is_unique[accounts.email]'
+					),
+					array(
+						'field' => 'email2',
+						'label' => 'Confirm Email',
+						'rules' => 'trim|required|max_length[255]|valid_email|matches[email]'
+					),
+					array(
+						'field' => 'password',
+						'label' => 'Password',
+						'rules' => 'required'
+					),
+					array(
+						'field' => 'password2',
+						'label' => 'Confirm Password',
+						'rules' => 'required|matches[password]'
+					),
+					array(
+						'field' => 'username',
+						'label' => 'Username',
+						'rules' => 'trim|max_length[255]|is_unique[accounts.username]'
+					),
+					array(
+						'field' 	=> 'display_name',
+						'label' => 'Display Name',
+						'rules' => 'trim|required|max_length[20]|is_unique[accounts.display_name]'
+					),
+					array(
+						'field' => 'account_type',
+						'label' => 'Account Type',
+						'rules' => 'check_enum[0,1]'
+					),
+					array(
+						'field' => 'realm',
+						'label' => 'Realm',
+						'rules' => 'check_enum[random,join,create]'
+					),
+					array(
+						'field' => 'terms',
+						'label' => 'Terms',
+						'rules' => 'check_terms'
+					)
+				));
+
+				// If account_type equals 1, also create a Dominion account in
+				// the current round
+				if ($this->input->post('account_type') === '1') {
+					// If we want to join a premade group
+					if ($this->input->post('realm') === 'join') {
+						$this->form_validation->set_rules(array(
+							array(
+								'field' => 'join_ream_id',
+								'label' => 'Realm ID',
+								'rules' => 'required|is_natural' // todo: check if realm id exists within current round
+							),
+							array(
+								'field' => 'join_group_password',
+								'label' => 'Group Password',
+								'rules' => 'trim|required' // todo: check for invalid group_password
+							)
+						));
+
+					// If we want to create a premade group
+					} elseif ($this->input->post('realm') === 'create') {
+						$this->form_validation->set_rules(array(
+							array(
+								'field' => 'create_realm_amount',
+								'label' => 'People Amount',
+								'rules' => 'required|greater_than[2]|less_than[6]'
+							),
+							array(
+								'field' => 'create_group_password',
+								'label' => 'Group Password',
+								'rules' => 'trim|required'
+							)
+						));
+					}
+
+					$this->form_validation->set_rules(array(
+						array(
+							'field' => 'dominion_name',
+							'label' => 'Dominion Name',
+							'rules' => 'trim|required|max_length[20]' // todo: unique?
+						),
+						array(
+							'field' => 'ruler_title',
+							'label' => 'Ruler Title',
+							'rules' => 'enum[Lord,Duke,Count,General,Captain,Minister,Lady,Dutchess,Countess]' // todo: set ruler_titles in db
+						),
+						array(
+							'field' => 'ruler_name',
+							'label' => 'Ruler Name',
+							'rules' => 'trim|max_length[20]'
+						),
+						array(
+							'field' => 'race',
+							'label' => 'Race',
+							'rules' => 'check_db[races.id]'
+						)
+					));
+				}
+
+				if (!$this->form_validation->run()) {
+					$message = validation_errors();
+					break;
+				}
+				// Generate activation code
+				$this->load->helper('string');
+				$activation_code = random_string('alnum', 8);
+
+				// Create OpenDominion accoount
+				$account = new Account();
+				$account->email             = $this->input->post('email');
+				$account->password          = sha1($this->input->post('password') . $this->config->item('password_salt'));
+				$account->username          = nempty_var($this->input->post('username'));
+				$account->display_name      = $this->input->post('display_name');
+				$account->activation_code   = $activation_code;
+				$account->registration_date = date('Y-m-d H:i:s');
+				$account->save();
+
+//				if ($this->input->poost('account_type') === '1') {
+					// Get vacant realm
+					$realm = Realm::get_vacant_realm();
+
+					// Create dominion
+					$dominion = new Dominion();
+					$dominion->account_id = $account->id;
+					$dominion->round_id   = $active_round->id;
+					$dominion->realm_id   = $realm->id;
+					$dominion->race_id    = $this->input->post('race');
+					$dominion->name       = $this->input->post('dominion_name');
+					$dominion->ruler_name = (($this->input->post('ruler_name') !== '') ? ($this->input->post('ruler_title') . ' ' . $this->input->post('ruler_name')) : $this->input->post('display_name'));
+					$dominion->save();
+
+					// Give 10 bonus land to home land type
+					$race = new Race($dominion->race_id);
+
+					// Get dominion with default data MySQL filled in
+					$dominion = new Dominion($dominion->id);
+
+					$dominion->{'land_' . $race->home_land_type} += 10;
+					$dominion->save();
+//				}
+
+				// Send activation email
+				$this->load->library('email');
+
+				$activation_link = base_url("account/activate/{$account->id}/{$activation_code}");
+
+				$this->email->from('info@opendominion.net', 'OpenDominion');
+				$this->email->to($this->input->post('email'));
+				$this->email->subject('Activate your OpenDominion account');
+				$this->email->message("Thank you for registering an OpenDominion account!\n\nPlease click the following link to activate your account: {$activation_link}");
+
+				// If the activation mail fails to send, activate account
+				// automatically
+				if (!@$this->email->send()) {
+					$account->active = 1;
+					$account->save();
+
+					$message = 'Registration successful. However, the activation email was failed to sent. Your account has been automatically activated.';
+				} else {
+					$message = 'Registration successful. Please check your email for the activation link.';
+				}
+			} while(0);
+
+			$this->set_content_vars('message', $message);
+		}
+
+		$this->set_content_vars(array(
+			'active_round' => $active_round,
+			'next_round'   => Round::get_next_round()
+		));
+
+		$this->parse('account/register');
+	}
+
+	/**
+	 */
+	public function activate($account_id, $activation_code)
+	{
+	}
+
+	/**
+	 */
+	public function logout()
+	{
+		$this->session->unset_userdata('login');
+		redirect();
+	}
+
+	/**
+	 */
+	public function forgot_password()
+	{
+	}
+
+	/**
+	 */
+	public function ajax_check_email()
+	{
+	}
+
+	/**
+	 */
+	public function ajax_check_username()
+	{
+	}
+
+	/**
+	 */
+	public function ajax_check_display_name()
+	{
+	}
+}

File opendominion/controllers/advisors.php

 {
 	/**
 	 */
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->set_content_vars('subview_header', $this->get_subview('advisors/_header'));
+	}
+
+	/**
+	 */
 	public function index()
 	{
 		redirect('advisors/production');
 	 */
 	public function production()
 	{
-		$this->load->model('ProductionModel');
-
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
-
-//var_dump($this->DominionModel->get_population_peasant_growth($dominion_id));exit;
-
-		$this->set_content_vars(array(
-			'subview_header'      => $this->get_subview('advisors/_header'),
-			'platinum_production' => $this->ProductionModel->get_platinum_production_cached($dominion_id),
-			'food_production'     => $this->ProductionModel->get_food_production_cached($dominion_id),
-			'food_consumption'    => $this->ProductionModel->get_food_consumption_cached($dominion_id),
-			'food_decay'          => $this->ProductionModel->get_food_decay_cached($dominion_id),
-			'food_net_change'     => $this->ProductionModel->get_food_net_change_cached($dominion_id),
-			'lumber_production'   => $this->ProductionModel->get_lumber_production_cached($dominion_id),
-			'lumber_decay'        => $this->ProductionModel->get_lumber_decay_cached($dominion_id),
-			'lumber_net_change'   => $this->ProductionModel->get_lumber_net_change_cached($dominion_id),
-			'mana_production'     => $this->ProductionModel->get_mana_production_cached($dominion_id),
-			'mana_decay'          => $this->ProductionModel->get_mana_decay_cached($dominion_id),
-			'mana_net_change'     => $this->ProductionModel->get_mana_net_change_cached($dominion_id),
-			'ore_production'      => $this->ProductionModel->get_ore_production_cached($dominion_id),
-			'gem_production'      => $this->ProductionModel->get_gem_production_cached($dominion_id),
-
-			'max_population'      => $this->DominionModel->get_population_max_cached($dominion_id),
-			'max_peasant_population' => ($this->DominionModel->get_population_max_cached($dominion_id) - $this->DominionModel->get_population_military_cached($dominion_id)),
-			'total_jobs'          => $this->DominionModel->get_population_jobs_cached($dominion_id),
-			'jobs_available'      => ($this->DominionModel->get_population_jobs_cached($dominion_id) - $this->DominionModel->get_population_employed($dominion_id))
-		));
-
 		$this->parse('advisors/production');
 	}
 
 	 */
 	public function land()
 	{
-		$this->load->model(array('LandModel', 'ExplorationModel'));
-
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
-
 		$this->set_content_vars(array(
-			'subview_header'    => $this->get_subview('advisors/_header'),
-			'total_land'        => $this->LandModel->get_total_land_cached($dominion_id),
-			'land_types'        => $this->LandModel->get_land_types(),
-			'barren_land'       => $this->LandModel->get_barren_land_cached($dominion_id),
-			'exploration_queue' => $this->ExplorationModel->get_exploration_queue_cached($dominion_id)
+			'barren_land'       => $this->dominion->get_barren_land(),
+			'exploration_queue' => $this->dominion->get_exploration_queue()
 		));
-
 		$this->parse('advisors/land');
 	}
 
 	 */
 	public function construction()
 	{
-		$this->load->model(array('LandModel', 'BuildingModel', 'ConstructionModel'));
-
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
-
-		$this->set_content_vars(array(
-			'subview_header'     => $this->get_subview('advisors/_header'),
-			'total_land'         => $this->LandModel->get_total_land_cached($dominion_id),
-			'building_types'     => $this->BuildingModel->get_building_types(),
-			'construction_queue' => $this->ConstructionModel->get_construction_queue($dominion_id),
-		));
-
+		$this->set_content_vars('construction_queue', $this->dominion->get_construction_queue());
 		$this->parse('advisors/construction');
 	}
 

File opendominion/controllers/bank.php

 	 */
 	public function index()
 	{
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				// Check input
+				if (!$this->input->post('from') || !$this->input->post('to') || !$this->input->post('amount')) {
+					$message = "Investment aborted due to bad input.";
+					break;
+				}
 
-		if ($this->input->post() !== false) {
-			if ($this->DominionModel->exchange($this->input->post(), $message)) {
-				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
-			}
+				$from   = $this->input->post('from');
+				$to     = $this->input->post('to');
+				$amount = $this->input->post('amount');
+
+				if (!in_array($from, array('platinum', 'lumber', 'ore', 'gems'))) {
+					$message = "Bad from";
+					break;
+				}
+
+				if (!in_array($to, array('platinum', 'lumber', 'ore', 'food'))) {
+					$message = "Bad to";
+					break;
+				}
+
+				$amount = (int)$amount;
+
+				if ($amount === 0) {
+					$message = "Investment aborted due to bad input.";
+					break;
+				}
+
+				// Check if we have enough resources
+				if ($amount > $this->dominion->{'resource_' . $from}) {
+					$message = "Investment aborted due to bad input.";
+					break;
+				}
+
+				// Calculate ratio
+
+				// 1:2
+				if (($from == 'gems') && in_array($to, array('platinum', 'lumber', 'ore'))) {
+					$multiplier = 2;
+
+				// 1:1
+				} elseif (($from == 'gems') && ($to == 'food')) {
+					$multiplier = 1;
+
+				// 2:1
+				} elseif (
+					(($from == 'platinum') && in_array($to, array('lumber', 'ore')))
+					|| (($from == 'lumber') && in_array($to, array('platinum', 'ore')))
+					|| (($from == 'ore') && in_array($to, array('platinum', 'lumber')))
+				) {
+					$multiplier = 0.5;
+
+				// 4:1
+				} elseif (in_array($from, array('platinum', 'lumber', 'ore')) && ($to == 'food')) {
+					$multiplier = 0.25;
+
+				// Invalid
+				} else {
+					$message = "Investment aborted due to bad input.";
+					return false;
+				}
+
+				$to_amount = floor($amount * $multiplier);
+
+				// Update dominion
+				$this->dominion->{'resource_' . $from} -= $amount;
+				$this->dominion->{'resource_' . $to} += $to_amount;
+				$this->dominion->save();
+
+				$message = ("You exchanged " . number_format($amount) . " {$from} into " . number_format($to_amount) . " {$to}.");
+
+				// Get updated dominion
+				$this->dominion->get($this->dominion->id);
+			} while (0);
 
 			$this->set_content_vars('message', $message);
 		}

File opendominion/controllers/construction.php

 	 */
 	public function index()
 	{
-		$this->load->model(array('RaceModel', 'ConstructionModel'));
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				// Check input
+				if (!$this->input->post('construct') || !is_array($this->input->post('construct'))) {
+					$message = 'Construction was not begun due to bad input.';
+					break;
+				}
 
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
+				$construct      = $this->input->post('construct');
+				$building_types = Dominion::get_building_types();
+				$barren_land    = $this->dominion->get_barren_land();
 
-		if ($this->input->post() !== false) {
-			if ($this->ConstructionModel->construct($this->input->post(), $message)) {
-				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
-			}
+				foreach ($construct as $building_type => $amount) {
+					if (!isset($building_types[$building_type])) {
+						$message = "'{$building}' isn't a building type.";
+						break 2;
+					}
+
+					if (($amount !== '') && !ctype_digit($amount)) {
+						$message = "Amount for land type '{$land_type}' isn't numeric.";
+						break 2;
+					}
+
+					if ($amount > $barren_land[$this->dominion->get_land_type_by_building($building_type)]) {
+						$message = "Construction not started due to lack of barren land.";
+						break 2;
+					}
+				}
+
+				$construct       = array_map('intval', $construct);
+				$total_construct = array_sum($construct);
+
+				if ($total_construct === 0) {
+					$message = "Construction was not begun due to bad input.";
+					break;
+				}
+
+				if ($total_construct > $this->dominion->get_construction_max_afford()) {
+					$message = "Construction not started due to lack of barren land.";
+					break;
+				}
+
+				$platinum_cost = ($total_construct * $this->dominion->get_construction_cost_platinum());
+				$lumber_cost   = ($total_construct * $this->dominion->get_construction_cost_lumber());
+
+				// todo: datamapper
+
+				$this->db->trans_start();
+
+				// Update dominion
+				$this->dominion->resource_platinum -= $platinum_cost;
+				$this->dominion->resource_lumber   -= $lumber_cost;
+				$this->dominion->save();
+
+				// Check for existing queue
+				$query = $this->db->get_where('queue_construction', array('dominion_id' => $this->dominion->id, 'hours' => 12), count($building_types));
+
+				$new_construct = $construct;
+
+				if ($query->num_rows() > 0) {
+					foreach ($query->result() as $row) {
+						$new_construct[$row->building_type] += $row->amount;
+					}
+				}
+
+				foreach ($new_construct as $building_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					// todo: insert into .. on duplicate key update?
+					$this->db->replace('queue_construction', array(
+						'dominion_id'   => $this->dominion->id,
+						'building_type' => $building_type,
+						'amount'        => $amount,
+						'hours'         => 12
+					));
+				}
+
+				$this->db->trans_complete();
+
+				// todo: optimize?
+				$tmp = array();
+				foreach ($construct as $building_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					$tmp[] = ($amount . ' ' . $building_types[$building_type]);
+				}
+				$construct_string = strtolower(strrev(preg_replace(strrev('/, /'), strrev(' and '), strrev(implode(', ', $tmp)), 1)));
+
+				$message = "Construction of {$construct_string} started at a cost of " . number_format($platinum_cost) . " platinum and ". number_format($lumber_cost) . " lumber.";
+
+				// Get updated dominion
+				$this->dominion->get($this->dominion->id);
+			} while (0);
 
 			$this->set_content_vars('message', $message);
 		}
 
 		$this->set_content_vars(array(
-			'race'                       => $this->RaceModel->get_dominion_race_cached($dominion_id),
-			'total_land'                 => $this->LandModel->get_total_land_cached($dominion_id),
-			'barren_land'                => $this->LandModel->get_barren_land_cached($dominion_id),
-			'construction_queue'         => $this->ConstructionModel->get_construction_queue_cached($dominion_id),
-			'construction_cost_platinum' => $this->ConstructionModel->get_construction_platinum_cost_cached($dominion_id),
-			'construction_cost_lumber'   => $this->ConstructionModel->get_construction_lumber_cost_cached($dominion_id),
-			'construction_max_afford'    => $this->ConstructionModel->get_construction_max_afford_cached($dominion_id)
+			'barren_land'        => $this->dominion->get_barren_land(),
+			'construction_queue' => $this->dominion->get_construction_queue()
 		));
-
 		$this->parse('construction/index');
 	}
 
 	 */
 	public function destroy()
 	{
-		$this->load->model(array('BuildingModel', 'ConstructionModel'));
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				// Check input
+				if (!$this->input->post('destroy') || !is_array($this->input->post('destroy'))) {
+					$message = 'The destruction was not completed due to incorrect input.';
+					break;
+				}
 
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
+				$destroy        = $this->input->post('destroy');
+				$building_types = Dominion::get_building_types();
 
-		if ($this->input->post() !== false) {
-			if ($this->ConstructionModel->destroy($this->input->post(), $message)) {
-				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
-			}
+				foreach ($destroy as $building => $amount) {
+					if (!isset($building_types[$building])) {
+						$message = "'{$building}' isn't a building type.";
+						break 2;
+					}
+
+					if (($amount !== '') && !ctype_digit($amount)) {
+						$message = "Amount for building '{$building}' isn't numeric.";
+						break 2;
+					}
+
+					if ($amount > $this->dominion->{'building_' . $building}) {
+						$message = "The destruction was not completed due to incorrect input.";
+						break 2;
+					}
+				}
+
+				$destroy       = array_map('intval', $destroy);
+				$total_destroy = array_sum($destroy);
+
+				if ($total_destroy === 0) {
+					$message = "The destruction was not completed due to incorrect input.";
+					break;
+				}
+
+				// Update dominion
+				foreach ($destroy as $building => $amount) {
+					$this->dominion->{'building_' . $building} -= $amount;
+				}
+				$this->dominion->save();
+
+				// todo: optimize?
+				$tmp = array();
+				foreach ($destroy as $building_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					$tmp[] = ($amount . ' ' . $building_types[$building_type]);
+				}
+				$destroy_string = strtolower(strrev(preg_replace(strrev('/, /'), strrev(' and '), strrev(implode(', ', $tmp)), 1)));
+
+				$message = "Destruction of {$destroy_string} is complete.";
+
+				// Get updated dominion
+				$this->dominion->get($this->dominion->id);
+			} while (0);
 
 			$this->set_content_vars('message', $message);
 		}
 
-		$this->set_content_vars(array(
-			'building_types' => $this->BuildingModel->get_building_types()
-		));
+//		$this->load->model(array('BuildingModel', 'ConstructionModel'));
+
+//		$dominion_id = $this->AuthenticationModel->get_dominion_id();
+
+//		if ($this->input->post() !== false) {
+//			if ($this->ConstructionModel->destroy($this->input->post(), $message)) {
+//				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
+//			}
+
+//			$this->set_content_vars('message', $message);
+//		}
+
+//		$this->set_content_vars(array(
+//			'building_types' => $this->BuildingModel->get_building_types()
+//		));
 
 		$this->parse('construction/destroy');
 	}

File opendominion/controllers/explore.php

 	 */
 	public function index()
 	{
-		$this->load->model(array('LandModel', 'ExplorationModel'));
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				// Check input
+				if (!$this->input->post('explore') || !is_array($this->input->post('explore'))) {
+					$message = 'Exploration was not begun due to bad input.';
+					break;
+				}
 
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
+				$explore    = $this->input->post('explore');
+				$land_types = Dominion::get_land_types();
 
-		if ($this->input->post() !== false) {
-			if ($this->ExplorationModel->explore($this->input->post(), $message)) {
-				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
-			}
+				foreach ($explore as $land_type => $amount) {
+					if (!isset($land_types[$land_type])) {
+						$message = "'{$land_type}' isn't a land type.";
+						break;
+					}
+
+					if (($amount !== '') && !ctype_digit($amount)) {
+						$message = "Amount for land type '{$land_type}' isn't numeric.";
+						break;
+					}
+				}
+
+				$explore       = array_map('intval', $explore);
+				$total_explore = array_sum($explore);
+
+				if ($total_explore === 0) {
+					$message = 'Exploration was not begun due to bad input.';
+					break;
+				}
+
+				if ($total_explore > $this->dominion->get_exploration_max_afford()) {
+					$message = "You do not have enough platinum/draftees to explore for {$total_explore} acres.";
+					break;
+				}
+
+				$platinum_cost = ($total_explore * $this->dominion->get_exploration_cost_platinum());
+				$draftee_cost  = ($total_explore * $this->dominion->get_exploration_cost_draftees());
+				$new_morale    = max(0, $this->dominion->morale - ($total_explore * Dominion::get_exploration_morale_drop($total_explore)));
+				$morale_drop   = ($this->dominion->morale - $new_morale);
+
+				// todo: datamapper
+
+				// Update dominion
+//				$this->dominion->resource_platinum -= $platinum_cost;
+//				$this->dominion->draftees          -= $draftee_cost;
+//				$this->dominion->morale             = $new_morale;
+//				$this->dominion->save();
+
+				$this->db->trans_start();
+
+				// Update dominion
+				$this->db->where('id', $this->dominion->id);
+				$this->db->update('dominions', array(
+					'resource_platinum' => ($this->dominion->resource_platinum - $platinum_cost),
+					'draftees'          => ($this->dominion->draftees          - $draftee_cost),
+					'morale'            => $new_morale
+				));
+
+				// Check for existing queue
+				$query = $this->db->get_where('queue_exploration', array('dominion_id' => $this->dominion->id, 'hours' => 12), count($land_types));
+
+				$new_explore = $explore;
+
+				if ($query->num_rows() > 0) {
+					foreach ($query->result() as $row) {
+						$new_explore[$row->land_type] += $row->amount;
+					}
+				}
+
+				foreach ($new_explore as $land_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					// todo: insert into .. on duplicate key update?
+					$this->db->replace('queue_exploration', array(
+						'dominion_id' => $this->dominion->id,
+						'land_type'   => $land_type,
+						'amount'      => $amount,
+						'hours'       => 12
+					));
+				}
+
+				$this->db->trans_complete();
+
+				// todo: optimize?
+				$tmp = array();
+				foreach ($explore as $land_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					$tmp[] = ($amount . ' ' . $land_types[$land_type]);
+				}
+				$explore_string = strtolower(strrev(preg_replace(strrev('/, /'), strrev(' and '), strrev(implode(', ', $tmp)), 1)));
+
+				$message = "Exploration for {$explore_string} begun at a cost of " . number_format($platinum_cost) . " platinum and " . number_format($draftee_cost) . " draftees. Your orders for exploration disheartens the military, and morale drops {$morale_drop}%.";
+
+				// Get updated dominion
+				$this->dominion->get($this->dominion->id);
+			} while (0);
 
 			$this->set_content_vars('message', $message);
 		}
 
-		$this->set_content_vars(array(
-			'land'                  => $this->LandModel->get_total_land_cached($dominion_id),
-			'land_types'            => $this->LandModel->get_land_types(),
-			'exploration_queue'     => $this->ExplorationModel->get_exploration_queue_cached($dominion_id),
-			'explore_cost_platinum' => $this->ExplorationModel->get_explore_platinum_cost_cached($dominion_id),
-			'explore_cost_draftees' => $this->ExplorationModel->get_explore_draftee_cost_cached($dominion_id),
-			'explore_max_afford'    => $this->ExplorationModel->get_explore_max_afford_cached($dominion_id)
-		));
-
+		$this->set_content_vars('exploration_queue', $this->dominion->get_exploration_queue());
 		$this->parse('explore');
 	}
 }

File opendominion/controllers/home.php

+<?php
+/**
+ * OpenDominion
+ *
+ * An open source recreation of the fantasy wargame Dominion.
+ *
+ * @package OpenDominion
+ * @author  WaveHack <email@wavehack.net>
+ * @license http://www.dbad-license.org/ DBAD Public License
+ * @link    https://bitbucket.org/WaveHack/opendominion
+ */
+/**
+ * Home controller
+ *
+ * @package OpenDominion\Controllers
+ */
+class Home extends FrontendController
+{
+	/**
+	 */
+	public function index()
+	{
+		if (is_logged_in()) {
+			redirect('status');
+		}
+
+		$this->parse('home');
+	}
+}

File opendominion/controllers/military.php

 	 */
 	public function index()
 	{
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
-		$dominion    = $this->DominionModel->get_dominion($dominion_id);
 
-		if ($this->input->post() !== false) {
-			if ($this->DominionModel->change_draft_rate($this->input->post(), $message)) {
-				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
-			}
+		// On submit - change draft rate
+		if ($this->input->post('draft_rate') !== false) {
+			// Custom scope
+			do {
+				// Check input
+				$draft_rate = $this->input->post('draft_rate');
+
+				if (($draft_rate < 0) || ($draft_rate > 100)) {
+					$message = "Draft Rate change aborted due to bad input.";
+					break;
+				}
+
+				$this->dominion->draft_rate = $draft_rate;
+				$this->dominion->save();
+
+				$message = "Draft Rate changed to {$draft_rate}%.";
+			} while (0);
 
 			$this->set_content_vars('message', $message);
 		}
 
-		$this->set_content_vars(array(
-			'peasants'            => $dominion->peasants,
-			'peasant_percentage'  => (($dominion->peasants / $this->DominionModel->get_population_cached($dominion_id)) * 100),
-			'military'            => $this->DominionModel->get_population_military_cached($dominion_id),
-			'military_percentage' => (($this->DominionModel->get_population_military_cached($dominion_id) / $this->DominionModel->get_population_cached($dominion_id)) * 100),
-		));
+		// On submit - training
+		if ($this->input->post('train') !== false) {
+			// todo
+		}
 
 		$this->parse('military/index');
 	}

File opendominion/controllers/rezone.php

 	 */
 	public function index()
 	{
-		$this->load->model('LandModel');
+		// On submit
+		if ($this->input->post() !== false) {
+			// Custom scope
+			do {
+				// Check input
+				if (!$this->input->post('rezone_from') ||  !$this->input->post('rezone_to') || !is_array($this->input->post('rezone_from')) || !is_array($this->input->post('rezone_to'))) {
+					$message = "Re-zone aborted due to bad input.";
+					break;
+				}
 
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
+				$rezone_from = $this->input->post('rezone_from');
+				$rezone_to   = $this->input->post('rezone_to');
+				$land_types  = Dominion::get_land_types();
 
-		if ($this->input->post() !== false) {
-			if ($this->LandModel->rezone($this->input->post(), $message)) {
-				$this->set_page_vars('dominion', $this->DominionModel->get_dominion($dominion_id));
-			}
+				foreach ($rezone_from as $land_type => $amount) {
+					if (!isset($land_types[$land_type])) {
+						$message = "'{$land_type}' isn't a land type.";
+						break 2;
+					}
+
+					if (($amount !== '') && !ctype_digit($amount)) {
+						$message = "Amount for land type '{$land_type}' isn't numeric.";
+						break 2;
+					}
+
+					if ($amount > $this->dominion->{'land_' . $land_type}) {
+						$message = "Not enough land to rezone.";
+						break 2;
+					}
+				}
+
+				foreach ($rezone_to as $land_type => $amount) {
+					if (!isset($land_types[$land_type])) {
+						$message = "'{$land_type}' isn't a land type.";
+						break 2;
+					}
+
+					if (($amount !== '') && !ctype_digit($amount)) {
+						$message = "Amount for land type '{$land_type}' isn't numeric.";
+						break 2;
+					}
+				}
+
+				$rezone_from       = array_map('intval', $rezone_from);
+				$rezone_to         = array_map('intval', $rezone_to);
+				$total_rezone_from = array_sum($rezone_from);
+				$total_rezone_to   = array_sum($rezone_to);
+
+				if (($total_rezone_from === 0) || ($total_rezone_from !== $total_rezone_to)) {
+					$message = "Re-zone aborted due to bad input.";
+					break;
+				}
+
+				$platinum_cost = ($total_rezone_from * $this->dominion->get_rezoning_cost_platinum());
+
+				if ($platinum_cost > $this->dominion->resource_platinum) {
+					$message = "Not enough platinum to rezone {$total_rezone_from} land";
+					break;
+				}
+
+				// Update dominion
+				foreach ($land_types as $land_field => $land_label) {
+					$this->dominion->{'land_' . $land_field} -= $rezone_from[$land_field];
+					$this->dominion->{'land_' . $land_field} += $rezone_to[$land_field];
+				}
+				$this->dominion->resource_platinum -= $platinum_cost;
+				$this->dominion->save();
+
+				// todo: optimize?
+				$tmp = array();
+				foreach ($rezone_from as $land_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					$tmp[] = ($amount . ' ' . $land_types[$land_type]);
+				}
+				$rezone_from_string = strtolower(strrev(preg_replace(strrev('/, /'), strrev(' and '), strrev(implode(', ', $tmp)), 1)));
+
+				$tmp = array();
+				foreach ($rezone_to as $land_type => $amount) {
+					if ($amount == 0) {
+						continue;
+					}
+
+					$tmp[] = ($amount . ' ' . $land_types[$land_type]);
+				}
+				$rezone_to_string = strtolower(strrev(preg_replace(strrev('/, /'), strrev(' and '), strrev(implode(', ', $tmp)), 1)));
+
+				$message = "Re-zoning of {$rezone_from_string} to {$rezone_to_string} completed at a cost of {$platinum_cost} platinum.";
+
+				// Get updated dominion
+				$this->dominion->get($this->dominion->id);
+			} while(0);
 
 			$this->set_content_vars('message', $message);
 		}
 
 		$this->set_content_vars(array(
-			'land_types'           => $this->LandModel->get_land_types(),
-			'barren_land'          => $this->LandModel->get_barren_land_cached($dominion_id, true),
-			'rezone_platinum_cost' => $this->LandModel->get_rezone_platinum_cost($dominion_id),
-			'rezone_max_afford'    => $this->LandModel->get_rezone_max_afford($dominion_id)
+			'land_types'  => Dominion::get_land_types(),
+			'barren_land' => $this->dominion->get_barren_land()
 		));
-
 		$this->parse('rezone');
 	}
 }

File opendominion/controllers/status.php

 	 */
 	public function index()
 	{
-		$this->load->model(array('RealmModel', 'RaceModel', 'LandModel', 'UnitModel'));
-
-		$dominion_id = $this->AuthenticationModel->get_dominion_id();
-
-		$this->set_content_vars(array(
-			'realm'                 => $this->RealmModel->get_realm_cached($dominion_id),
-			'race'                  => $this->RaceModel->get_dominion_race_cached($dominion_id),
-			'land'                  => $this->LandModel->get_total_land_cached($dominion_id),
-			'units'                 => $this->UnitModel->get_units_cached($dominion_id),
-			'employment_percentage' => $this->DominionModel->get_population_employment_percentage_cached($dominion_id)
-		));
+		$this->dominion->realm->get();
+		$this->dominion->race->get();
+		$this->dominion->race->get_units();
 
 		$this->parse('status');
 	}

File opendominion/core/frontendcontroller.php

 	/**
 	 * Constructor
 	 *
-	 * Loads the necessary libraries, helpers, models, etc. And sets b*asic page
+	 * Loads the necessary libraries, helpers, models, etc. And sets basic page
 	 * variables needed in every request.
 	 */
 	public function __construct()
 		parent::__construct();
 
 		$this->load->database();
-		$this->load->library('session');
-		$this->load->helper(array('wch_general', 'url'));
-		$this->load->model('AuthenticationModel');
-
-		// Temp
-		if ($this->input->get('template')) {
-			$this->template = $this->input->get('template');
-		}
+		$this->load->library(array('datamapper', 'session'));
+		$this->load->helper(array('url', 'opendominion', 'wch_general'));
 
 		// Default page vars
 		$this->set_page_vars(array(
 			'header'   => array(
 				'title' => 'OpenDominion'
 			),
-			'logged_in' => $this->AuthenticationModel->is_logged_in()
+			'logged_in' => false
 		));
 
 		// Prohibit URLs starting with /index.php/
 			$this->show_improved_404();
 		}
 
-		if ($this->AuthenticationModel->is_logged_in()) {
-			$this->load->model('AccountModel');
-
-			// If Dominion exists (i.e. the user is playing)
-			if ($this->AccountModel->has_active_dominion($this->AuthenticationModel->get_account_id())) {
-				$this->load->model('DominionModel');
-
-				// Additional page vars on login
-				$this->set_page_vars(array(
-					'dominion' => $this->DominionModel->get_dominion_cached($this->AuthenticationModel->get_dominion_id()),
-					'networth' => $this->DominionModel->get_networth_cached($this->AuthenticationModel->get_dominion_id())
-				));
-			}
-		}
-
 		// Enable profiler on ?debug
 		if (isset($_GET['debug'])) {
 			$this->output->enable_profiler(1);
 	 */
 	protected function set_page_vars($data, $value = null)
 	{
-//		$this->page_vars = array_extend($this->page_vars, (array)$data);
-
 		if (is_array($data)) {
 			$this->page_vars = array_extend($this->page_vars, $data);
 		} else {
 	 */
 	protected function set_content_vars($data, $value = null)
 	{
-	//		$this->content_vars = array_extend($this->content_vars, (array)$data);
-
 		if (is_array($data)) {
 			$this->content_vars = array_extend($this->content_vars, $data);
 		} else {
 	 */
 	protected function parse($view)
 	{
-		// Set UTF-8
+		// Set content-type
 		header('Content-Type: text/html; charset=utf-8');
 
 		$this->load->view("{$this->template}/page.php", array_extend($this->page_vars, array('_content' => $this->load->view("{$this->template}/pages/{$view}", array_extend($this->page_vars, $this->content_vars), true))));
 	{
 		// Page vars
 		$this->set_page_vars('head', array(
-			'title'       => 'Error 404 - OpenDominion',
-			'robots'      => 'noindex'
+			'title'  => 'Error 404 - OpenDominion',
+			'robots' => 'noindex'
 		));
 
 		// Set HTTP 404 status header

File opendominion/core/frontendlogincontroller.php

  */
 abstract class FrontendLoginController extends FrontendController
 {
+	/** @var object Dominion model object of the logged in user */
+	protected $dominion;
+
 	/**
 	 * Constructor
 	 *
-	 * If the user is not logged in, redirect to the root.
+	 * Does some default stuff for when the user is logged in after checking
+	 * login. Loads Dominion object.
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
-		if (!$this->AuthenticationModel->is_logged_in()) {
+		// Redirect to root if the user is not logged in
+		if (!is_logged_in()) {
 			redirect();
 		}
+
+		// If user has a Dominion
+		if (get_login_dominion_id() !== null) {
+			$this->dominion = new Dominion(get_login_dominion_id());
+
+			$this->set_page_vars('dominion', $this->dominion);
+		}
+
 	}
 }

File opendominion/datamapper/array.php

+<?php
+
+/**
+ * Array Extension for DataMapper classes.
+ *
+ * Quickly convert DataMapper models to-and-from PHP arrays.
+ *
+ * @license 	MIT License
+ * @package		DMZ-Included-Extensions
+ * @category	DMZ
+ * @author  	Phil DeJarnett
+ * @link    	http://www.overzealous.com/dmz/pages/extensions/array.html
+ * @version 	1.0
+ */
+
+// --------------------------------------------------------------------------
+
+/**
+ * DMZ_Array Class
+ *
+ * @package		DMZ-Included-Extensions
+ */
+class DMZ_Array {
+
+	/**
+	 * Convert a DataMapper model into an associative array.
+	 * If the specified fields includes a related object, the ids from the
+	 * objects are collected into an array and stored on that key.
+	 * This method does not recursively add objects.
+	 *
+	 * @param	DataMapper $object The DataMapper Object to convert
+	 * @param	array $fields Array of fields to include.  If empty, includes all database columns.
+	 * @return	array An associative array of the requested fields and related object ids.
+	 */
+	function to_array($object, $fields = '')
+	{
+		// assume all database columns if $fields is not provided.
+		if(empty($fields))
+		{
+			$fields = $object->fields;
+		}
+		else
+		{
+			$fields = (array) $fields;
+		}
+
+		$result = array();
+
+		foreach($fields as $f)
+		{
+			// handle related fields
+			if(array_key_exists($f, $object->has_one) || array_key_exists($f, $object->has_many))
+			{
+				// each related item is stored as an array of ids
+				// Note: this method will NOT get() the related object.
+				$rels = array();
+				foreach($object->{$f} as $item)
+				{
+					$rels[] = $item->id;
+				}
+				$result[$f] = $rels;
+			}
+			else
+			{
+				// just the field.
+				$result[$f] = $object->{$f};
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Convert the entire $object->all array result set into an array of
+	 * associative arrays.
+	 *
+	 * @see		to_array
+	 * @param	DataMapper $object The DataMapper Object to convert
+	 * @param	array $fields Array of fields to include.  If empty, includes all database columns.
+	 * @return	array An array of associative arrays.
+	 */
+	function all_to_array($object, $fields = '')
+	{
+		// loop through each object in the $all array, convert them to
+		// an array, and add them to a new array.
+		$result = array();
+		foreach($object as $o)
+		{
+			$result[] = $o->to_array($fields);
+		}
+		return $result;
+	}
+
+	/**
+	 * Convert a single field from the entire $object->all array result set into an a single array
+	 * with the objects' id field as key
+	 *
+	 * @param	DataMapper $object The DataMapper Object to convert
+	 * @param	string $field to include
+	 * @return	array An array of associative arrays.
+	 */
+	function all_to_single_array($object, $field = '')
+	{
+		// loop through each object in the $all array, convert them to
+		// an array, and add them to a new array.
+		$result = array();
+		if ( ! empty($field) )
+		{
+			foreach($object as $o)
+			{
+				isset($o->{$field}) and $result[$o->id] = $o->{$field};
+			}
+		}
+		return $result;
+	}
+
+	/**
+	 * Convert an associative array back into a DataMapper model.
+	 *
+	 * If $fields is provided, missing fields are assumed to be empty checkboxes.
+	 *
+	 * @param	DataMapper $object The DataMapper Object to save to.
+	 * @param	array $data A an associative array of fields to convert.
+	 * @param	array $fields Array of 'safe' fields.  If empty, only includes the database columns.
+	 * @param	bool $save If TRUE, then attempt to save the object automatically.
+	 * @return	array|bool A list of newly related objects, or the result of the save if $save is TRUE
+	 */
+	function from_array($object, $data, $fields = '', $save = FALSE)
+	{
+		// keep track of newly related objects
+		$new_related_objects = array();
+
+		// Assume all database columns.
+		// In this case, simply store $fields that are in the $data array.
+		if(empty($fields))
+		{
+			$fields = $object->fields;
+			foreach($data as $k => $v) {
+				if(in_array($k, $fields))
+				{
+					$object->{$k} = $v;
+				}
+			}
+		}
+		else
+		{
+			// If $fields is provided, assume all $fields should exist.
+			foreach($fields as $f)
+			{
+				if(array_key_exists($f, $object->has_one))
+				{
+					// Store $has_one relationships
+					$c = get_class($object->{$f});
+					$rel = new $c();
+					$id = isset($data[$f]) ? $data[$f] : 0;
+					$rel->get_by_id($id);
+					if($rel->exists())
+					{
+						// The new relationship exists, save it.
+						$new_related_objects[$f] = $rel;
+					}
+					else
+					{
+						// The new relationship does not exist, delete the old one.
+						 $object->delete($object->{$f}->get());
+					}
+				}
+				else if(array_key_exists($f, $object->has_many))
+				{
+					// Store $has_many relationships
+					$c = get_class($object->{$f});
+					$rels = new $c();
+					$ids = isset($data[$f]) ? $data[$f] : FALSE;
+					if(empty($ids))
+					{
+						// if no IDs were provided, delete all old relationships.
+						$object->delete($object->{$f}->select('id')->get()->all);
+					}
+					else
+					{
+						// Otherwise, get the new ones...
+						$rels->where_in('id', $ids)->select('id')->get();
+						// Store them...
+						$new_related_objects[$f] = $rels->all;
+						// And delete any old ones that do not exist.
+						$old_rels = $object->{$f}->where_not_in('id', $ids)->select('id')->get();
+						$object->delete($old_rels->all);