From 9c80585ef947652fc2b181f8140f6a982c1e5399 Mon Sep 17 00:00:00 2001 From: Znote Date: Sun, 10 Mar 2019 18:53:24 +0100 Subject: [PATCH] Character Auction System --- admin_auction.php | 232 +++++++++ auctionChar.php | 965 ++++++++++++++++++++++++++++++++++-- config.php | 21 +- engine/database/connect.php | 15 + layout/menu.php | 3 + layout/widgets/Wadmin.php | 3 + 6 files changed, 1196 insertions(+), 43 deletions(-) create mode 100644 admin_auction.php diff --git a/admin_auction.php b/admin_auction.php new file mode 100644 index 0000000..99d9f3f --- /dev/null +++ b/admin_auction.php @@ -0,0 +1,232 @@ + 0) + $duration['hour'] = ($duration['day'] - (int)$duration['day']) * 24; + if (isset($duration['hour'])) { + if (($duration['hour'] - (int)$duration['hour']) > 0) + $duration['minute'] = ($duration['hour'] - (int)$duration['hour']) * 60; + if (isset($duration['minute'])) { + if (($duration['minute'] - (int)$duration['minute']) > 0) + $duration['second'] = ($duration['minute'] - (int)$duration['minute']) * 60; + } + } + $tmp = array(); + foreach ($duration as $type => $value) { + if ($value >= 1) { + $pluralType = ((int)$value === 1) ? $type : $type . 's'; + if ($type !== 'second') $tmp[] = (int)$value . " $pluralType"; + else $tmp[] = (int)$value . " $pluralType"; + } + } + return implode(', ', $tmp); +} +// start + +// Passive check to see if bid period has expired and someone won a deal +$time = time(); +$expired_auctions = mysql_select_multi(" + SELECT `id` + FROM `znote_auction_player` + WHERE `sold` = 0 + AND `time_end` < {$time} + AND `bidder_account_id` > 0 +"); +//data_dump($expired_auctions, $this_account_id, "expired_auctions"); +if ($expired_auctions !== false) { + $soldIds = array(); + foreach ($expired_auctions as $a) { + $soldIds[] = $a['id']; + } + if (!empty($soldIds)) { + mysql_update(" + UPDATE `znote_auction_player` + SET `sold`=1 + WHERE `id` IN(".implode(',', $soldIds).") + LIMIT ".COUNT($soldIds)."; + "); + } +} +// end passive check +// Pending auctions +$pending = mysql_select_multi(" + SELECT + `za`.`id` AS `zaid`, + `za`.`price`, + `za`.`bid`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`id` AS `player_id`, + `p`.`name`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`claimed` = 0 + AND `za`.`sold` = 1 + ORDER BY `za`.`time_end` desc +"); +// ongoing auctions +$ongoing = mysql_select_multi(" + SELECT + `za`.`id` AS `zaid`, + `za`.`price`, + `za`.`bid`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`sold` = 0 + ORDER BY `za`.`time_end` desc; +"); +// Completed auctions +$completed = mysql_select_multi(" + SELECT + `za`.`id` AS `zaid`, + `za`.`price`, + `za`.`bid`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`id` AS `player_id`, + `p`.`name`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `za`.`claimed` = 1 + ORDER BY `za`.`time_end` desc +"); +?> +

Character Auction History

+

Let players sell, buy and bid on characters. +
Creates a deeper shop economy, encourages players to spend more money in shop for points. +
Pay to win/progress mechanic, but also lets people who can barely afford points to gain it +
by leveling characters to sell. It can also discourages illegal/risky third-party account +
services. Since players can buy officially & support the server, dodgy competitors have to sell for cheaper. +
Without admin interference this is organic to each individual community economy inflation.

+ +

Pending orders to be claimed

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlayerLevelVocationPriceBid
Added:Ended:
+ + +

Ongoing auctions

+ + + + + + + + + + + + + + + + + + + + + + +
LevelVocationDetailsPriceBidAddedType
VIEW $character['time_end']) ? true : false; + echo getClock($character['time_begin'], true); + ?> + ('.toDuration(($character['time_end'] - time())).')'; ?>
+ + +

Completed auctions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlayerLevelVocationPriceBid
Added:Ended:
+ \ No newline at end of file diff --git a/auctionChar.php b/auctionChar.php index 79725f1..a391c41 100644 --- a/auctionChar.php +++ b/auctionChar.php @@ -1,45 +1,936 @@ 0) + $duration['hour'] = ($duration['day'] - (int)$duration['day']) * 24; + if (isset($duration['hour'])) { + if (($duration['hour'] - (int)$duration['hour']) > 0) + $duration['minute'] = ($duration['hour'] - (int)$duration['hour']) * 60; + if (isset($duration['minute'])) { + if (($duration['minute'] - (int)$duration['minute']) > 0) + $duration['second'] = ($duration['minute'] - (int)$duration['minute']) * 60; + } + } + $tmp = array(); + foreach ($duration as $type => $value) { + if ($value >= 1) { + $pluralType = ((int)$value === 1) ? $type : $type . 's'; + if ($type !== 'second') $tmp[] = (int)$value . " $pluralType"; + else $tmp[] = (int)$value . " $pluralType"; + } + } + return implode(', ', $tmp); +} +?> +

Character auction

+ -

Character auctioning

- - - - - - - - - - - - - - - -
NameLevelVocationImagePrice/Buy
Tester105SorcererVIEW
- - Character shop auction system is currently only available for ServerEngine TFS_10.

"; + include 'layout/overall/footer.php'; + die(); + } + if ((int)$auction['storage_account_id'] === (int)$this_account_id) { + echo "

The storage account cannot use the character auction.

"; + include 'layout/overall/footer.php'; + die(); + } + $step = $auction['step']; + $step_duration = $auction['step_duration']; + $actions = array( + 'list', // list all available players in auction + 'view', // view a specific player + 'create', // select which character to add and initial price + 'add', // add character to list + 'bid', // Bid or buy a specific player + 'refund', // Refund a player you added back to your account + 'claim' // Claim a character you won through purchase or bid + ); + + // Default action is list, but $_GET or $_POST will override it. + $action = 'list'; + // Load selected string from actions array based on input, strict whitelist validation + if (isset( $_GET['action']) && in_array( $_GET['action'], $actions)) { + $action = $actions[array_search( $_GET['action'], $actions, true)]; + } + if (isset($_POST['action']) && in_array($_POST['action'], $actions)) { + $action = $actions[array_search($_POST['action'], $actions, true)]; + } + + // Passive check to see if bid period has expired and someone won a deal + $time = time(); + $expired_auctions = mysql_select_multi(" + SELECT `id` + FROM `znote_auction_player` + WHERE `sold` = 0 + AND `time_end` < {$time} + AND `bidder_account_id` > 0 + "); + //data_dump($expired_auctions, $this_account_id, "expired_auctions"); + if ($expired_auctions !== false) { + $soldIds = array(); + foreach ($expired_auctions as $a) { + $soldIds[] = $a['id']; + } + if (!empty($soldIds)) { + mysql_update(" + UPDATE `znote_auction_player` + SET `sold`=1 + WHERE `id` IN(".implode(',', $soldIds).") + LIMIT ".COUNT($soldIds)."; + "); + } + } + // end passive check + + // If we bid or buy a character + // silently continues to list if buy, back to view if bid + if ($action === 'bid') { + //data_dump($_POST, false, "Bid or buying:"); + $zaid = (isset($_POST['zaid']) && (int)$_POST['zaid'] > 0) ? (int)$_POST['zaid'] : false; + $price = (isset($_POST['price']) && (int)$_POST['price'] > 0) ? (int)$_POST['price'] : false; + + $action = 'list'; + if ($zaid !== false && $price !== false) { + // The account of the buyer, if he can afford what he is trying to pay + $account = mysql_select_single(" + SELECT + `a`.`id`, + `za`.`points` + FROM `accounts` a + INNER JOIN `znote_accounts` za + ON `a`.`id` = `za`.`account_id` + WHERE `a`.`id`= {$this_account_id} + AND `za`.`points` >= {$price} + LIMIT 1; + "); + //data_dump($account, false, "Buyer account:"); + + // The character to buy, presuming it isn't sold, buyer isn't the owner, buyer can afford it + if ($account !== false) { + $character = mysql_select_single(" + SELECT + `za`.`id` AS `zaid`, + `za`.`player_id`, + `za`.`original_account_id`, + `za`.`bidder_account_id`, + `za`.`time_begin`, + `za`.`time_end`, + `za`.`price`, + `za`.`bid`, + `za`.`deposit`, + `za`.`sold` + FROM `znote_auction_player` za + WHERE `za`.`id` = {$zaid} + AND `za`.`sold` = 0 + AND `za`.`original_account_id` != {$this_account_id} + AND `za`.`price` <= {$price} + AND `za`.`bid`+{$step} <= {$price} + LIMIT 1 + "); + //data_dump($character, false, "Character to buy:"); + + if ($character !== false) { + // If auction already have a previous bidder, refund him his points + if ($character['bid'] > 0 && $character['bidder_account_id'] > 0) { + mysql_update(" + UPDATE `znote_accounts` + SET `points` = `points`+{$character['bid']} + WHERE `account_id` = {$character['bidder_account_id']} + LIMIT 1; + "); + // If previous bidder is not you, increase bidding period by 1 hour + // (Extending bid war to give bidding competitor a chance to retaliate) + if ((int)$character['bidder_account_id'] !== (int)$account['id']) { + mysql_update(" + UPDATE `znote_auction_player` + SET `time_end` = `time_end`+{$step_duration} + WHERE `id` = {$character['zaid']} + LIMIT 1; + "); + } + } + // Remove points from buyer + mysql_update(" + UPDATE `znote_accounts` + SET `points` = `points`-{$price} + WHERE `account_id` = {$account['id']} + LIMIT 1; + "); + // Update auction, and set new bidder data + $time = time(); + mysql_update(" + UPDATE `znote_auction_player` + SET + `bidder_account_id` = {$account['id']}, + `bid` = {$price}, + `sold` = CASE WHEN {$time} >= `time_end` THEN 1 ELSE 0 END + WHERE `id` = {$character['zaid']} + LIMIT 1; + "); + // If character is sold, return deposit back to sellers account + if (time() >= $character['time_end']) { + mysql_update(" + UPDATE `znote_accounts` + SET `points` = `points`+{$character['deposit']} + WHERE `account_id` = {$account['id']} + LIMIT 1; + "); + } else { + // If character is not sold, this is a bidding war, we want to send user back to view. + $action = 'view'; + } + // Note: Transferring character to the new account etc happens later in $action = 'claim' + } + } + } + } + + // See a specific character in auction, + // silently fallback to list if he doesn't exist or is already sold + if ($action === 'view') { // View a character in the auction + if (!isset($zaid)) { + $zaid = (isset($_GET['zaid']) && (int)$_GET['zaid'] > 0) ? (int)$_GET['zaid'] : false; + } + if ($zaid !== false) { + // Retrieve basic character information + $character = mysql_select_single(" + SELECT + `za`.`id` AS `zaid`, + `za`.`player_id`, + `za`.`original_account_id`, + `za`.`bidder_account_id`, + `za`.`time_begin`, + `za`.`time_end`, + CASE WHEN `za`.`price` > `za`.`bid` + THEN `za`.`price` + ELSE `za`.`bid`+{$step} + END AS `price`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN 1 + ELSE 0 + END AS `own`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN `p`.`name` + ELSE '' + END AS `name`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN `za`.`bid` + ELSE 0 + END AS `bid`, + CASE WHEN `za`.`original_account_id` = {$this_account_id} + THEN `za`.`deposit` + ELSE 0 + END AS `deposit`, + `p`.`vocation`, + `p`.`level`, + `p`.`balance`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons`, + `p`.`maglevel` AS `magic`, + `p`.`skill_fist` AS `fist`, + `p`.`skill_club` AS `club`, + `p`.`skill_sword` AS `sword`, + `p`.`skill_axe` AS `axe`, + `p`.`skill_dist` AS `dist`, + `p`.`skill_shielding` AS `shielding`, + `p`.`skill_fishing` AS `fishing` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `za`.`id` = {$zaid} + AND `za`.`sold` = 0 + LIMIT 1; + "); + //data_dump($character, false, "Character info"); + + if (is_array($character) && !empty($character)) { + // If the end of the bid is in the future, the bid is currently ongoing + $bidding_period = ((int)$character['time_end']+1 > time()) ? true : false; + $player_items = mysql_select_multi(" + SELECT `itemtype`, SUM(`count`) AS `count` + FROM `player_items` + WHERE `player_id` = {$character['player_id']} + GROUP BY `itemtype` + ORDER BY MIN(`pid`) ASC + "); + $depot_items = mysql_select_multi(" + SELECT `itemtype`, SUM(`count`) AS `count` + FROM `player_depotitems` + WHERE `player_id` = {$character['player_id']} + GROUP BY `itemtype` + ORDER BY MIN(`pid`) ASC + "); + $account = mysql_select_single(" + SELECT `points` + FROM `znote_accounts` + WHERE `account_id`={$this_account_id} + AND `points` >= {$character['price']} + LIMIT 1; + "); + ?> +

Detailed character information. Go back to list.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
LevelVocationImageBankPrice
+ img + points
+

Remaining bid period: .

+
+ + +

You have shop points remaining.

+ + +

So far so good! +
You currently have the highest bid at: +

+

If nobody bids higher than you, this character will be yours in: +
. +

+ +
+ + + > + + + + +
+ + +

So far so good! +
You currently have the highest bid at: +

+

If nobody bids higher than you, this character will be yours in: +
. +

+ +

You cannot afford to buy this character.

+ + +

You are the seller of this character. +
Name: +
Price: +
Bid: +
Deposit: + +

The bidding period has ended, you can wait until someone decides to instantly buy it, or you can reclaim your character to your account.

+
+ + + +
+ +

The bidding period will last for . After this period, you can reclaim your character if nobody has bid on it.

+ +

+ + + + + + + + + + + + +
Character skills:
magic
fist
club
sword
axe
dist
shielding
fishing
+ + + + + + + + + + + + + + + + + + + +
Player items:
ImageItemCount
" alt="Item Image">
+ + + + + + + + + + + + + + + + + + + +
Depot items:
ImageItemCount
" alt="Item Image">
+ 0) ? (int)$_POST['pid'] : false; + $cost = (isset($_POST['cost']) && (int)$_POST['cost'] > 0) ? (int)$_POST['cost'] : false; + $deposit = (int)$cost * ($auction['deposit'] / 100); + $password = SHA1($_POST['password']); + + // Verify values + $status = false; + $account = false; + if ($pid > 0 && $cost >= $auction['lowestPrice']) { + $account = mysql_select_single(" + SELECT `a`.`id`, `a`.`password`, `za`.`points` + FROM `accounts` a + INNER JOIN `znote_accounts` za + ON `a`.`id` = `za`.`account_id` + WHERE `a`.`id`= {$this_account_id} + AND `a`.`password`='{$password}' + AND `za`.`points` >= {$deposit} + LIMIT 1 + ;"); + if (isset($account['password']) && $account['password'] === $password) { + // Check if player exist, is offline and not already in auction + // And is not a tutor or a GM+. + $player = mysql_select_single(" + SELECT `p`.`id`, `p`.`name`, + CASE + WHEN `po`.`player_id` IS NULL + THEN 0 + ELSE 1 + END AS `online`, + CASE + WHEN `za`.`player_id` IS NULL + THEN 0 + ELSE 1 + END AS `alreadyInAuction` + FROM `players` p + LEFT JOIN `players_online` po + ON `p`.`id` = `po`.`player_id` + LEFT JOIN `znote_auction_player` za + ON `p`.`id` = `za`.`player_id` + AND `p`.`account_id` = `za`.`original_account_id` + AND `za`.`claimed` = 0 + WHERE `p`.`id` = {$pid} + AND `p`.`account_id` = {$this_account_id} + AND `p`.`group_id` = 1 + LIMIT 1 + ;"); + if (isset($player['online']) && $player['online'] == 0) { + if (isset($player['alreadyInAuction']) && $player['alreadyInAuction'] == 0) { + $status = true; + } + } + } + } + if ($status) { + $time_begin = time(); + $time_end = $time_begin + ($auction['biddingDuration']); + // Insert row to znote_auction_player + mysql_insert(" + INSERT INTO `znote_auction_player` ( + `player_id`, + `original_account_id`, + `bidder_account_id`, + `time_begin`, + `time_end`, + `price`, + `bid`, + `deposit`, + `sold`, + `claimed` + ) VALUES ( + {$pid}, + {$this_account_id}, + 0, + {$time_begin}, + {$time_end}, + {$cost}, + 0, + {$deposit}, + 0, + 0 + ); + "); + // Move player to storage account + mysql_update(" + UPDATE `players` + SET `account_id` = {$auction['storage_account_id']} + WHERE `id` = {$pid} + LIMIT 1; + "); + // Hide character from public character list (in pidprofile.php) + mysql_update(" + UPDATE `znote_players` + SET `hide_char` = 1 + WHERE `player_id` = {$pid} + LIMIT 1; + "); + // Remove deposit from account + $afterDeposit = $account['points'] - $deposit; + mysql_update(" + UPDATE `znote_accounts` + SET `points` = {$afterDeposit} + WHERE `account_id` = {$account['id']} + LIMIT 1; + "); + } + $action = 'list'; + } + + // If we are refunding a player back to its original owner + // silently continues to list + if ($action === 'refund') { + $zaid = (isset($_POST['zaid']) && (int)$_POST['zaid'] > 0) ? (int)$_POST['zaid'] : false; + //data_dump($_POST, false, "POST"); + if ($zaid !== false) { + $time = time(); + // If original account is the one trying to get it back, + // and bidding period is over, + // and its not labelled as sold + // and nobody has bid on it + $character = mysql_select_single(" + SELECT `player_id` + FROM `znote_auction_player` + WHERE `id`= {$zaid} + AND `original_account_id` = {$this_account_id} + AND `time_end` <= {$time} + AND `bidder_account_id` = 0 + AND `bid` = 0 + AND `sold` = 0 + LIMIT 1 + "); + //data_dump($character, false, "Character"); + if ($character !== false) { + // Move character to buyer account and give it a new name + mysql_update(" + UPDATE `players` + SET `account_id` = {$this_account_id} + WHERE `id` = {$character['player_id']} + LIMIT 1; + "); + // Set label to sold + mysql_update(" + UPDATE `znote_auction_player` + SET `sold` = 1 + WHERE `id`= {$zaid} + LIMIT 1; + "); + // Show character in public character list (in characterprofile.php) + mysql_update(" + UPDATE `znote_players` + SET `hide_char` = 0 + WHERE `player_id` = {$character['player_id']} + LIMIT 1; + "); + } + } + $action = 'list'; + } + + // If we are claiming a character + // If validation fails then explain why, but then head over to list regardless of status + if ($action === 'claim') { + $zaid = (isset($_POST['zaid']) && (int)$_POST['zaid'] > 0) ? (int)$_POST['zaid'] : false; + $name = (isset($_POST['name']) && !empty($_POST['name'])) ? getValue($_POST['name']) : false; + $errors = array(); + //data_dump($_POST, $name, "Post data:"); + if ($zaid === false) { + $errors[] = 'We are unable to find this auction order.'; + } + if ((int)$auction['storage_account_id'] === $this_account_id) { + $errors[] = 'Silly you! You cannot claim characters with the storage account configured in
$config[\'shop_auction\'][\'storage_account_id\']
because you already have those characters in your account! :P'; + if ($is_admin) { + $errors[] = "ADMIN: The storage account in config.php should not be the same as the admin account."; + } + } + if ($name === false) { + $errors[] = 'Please give the character a name.'; + } else { + // begin name validation + $name = validate_name($name); + if (user_character_exist($name) !== false) { + $errors[] = 'Sorry, that character name already exist.'; + } + if (!preg_match("/^[a-zA-Z_ ]+$/", $name)) { + $errors[] = 'Your name may only contain a-z, A-Z and spaces.'; + } + if (strlen($name) < $config['minL'] || strlen($name) > $config['maxL']) { + $errors[] = 'Your character name must be between ' . $config['minL'] . ' - ' . $config['maxL'] . ' characters long.'; + } + // name restriction + $resname = explode(" ", $name); + foreach($resname as $res) { + if(in_array(strtolower($res), $config['invalidNameTags'])) { + $errors[] = 'Your username contains a restricted word.'; + } + else if(strlen($res) == 1) { + $errors[] = 'Too short words in your name.'; + } + } + $name = format_character_name($name); + // end name validation + if (empty($errors)) { + // Make sure you have access to claim this zaid character. + // And that you havent already claimed it. + // And that the character isn't online... + $character = mysql_select_single(" + SELECT + `za`.`id` AS `zaid`, + `za`.`player_id`, + `p`.`account_id` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + LEFT JOIN `players_online` po + ON `p`.`id` = `po`.`player_id` + WHERE `za`.`id` = {$zaid} + AND `za`.`sold` = 1 + AND `p`.`account_id` != {$this_account_id} + AND `za`.`bidder_account_id` = {$this_account_id} + AND `po`.`player_id` IS NULL + "); + //data_dump($character, false, "Character"); + if ($character !== false) { + // Set character to claimed + mysql_update(" + UPDATE `znote_auction_player` + SET `claimed`='1' + WHERE `id` = {$character['zaid']} + "); + // Move character to buyer account and give it a new name + mysql_update(" + UPDATE `players` + SET `name` = '{$name}', + `account_id` = {$this_account_id} + WHERE `id` = {$character['player_id']} + LIMIT 1; + "); + // Show character in public character list (in characterprofile.php) + mysql_update(" + UPDATE `znote_players` + SET `hide_char` = 0 + WHERE `player_id` = {$character['player_id']} + LIMIT 1; + "); + // Remove character from other players VIP lists + mysql_delete(" + DELETE FROM `account_viplist` + WHERE `player_id` = {$character['player_id']} + "); + // Remove the character deathlist + mysql_delete(" + DELETE FROM `player_deaths` + WHERE `player_id` = {$character['player_id']} + "); + } else { + $errors[] = "You either don't have access to claim this character, or you have already claimed it, or this character isn't sold yet, or we were unable to find this auction order."; + if ($is_admin) { + $errors[] = "ADMIN: ... Or character is online."; + } + } + } + } + if (!empty($errors)) { + //data_dump($errors, false, "Errors:"); + ?> + + + + + + $error): ?> + + + + + +
#Issues occured while claiming your name
+ `za`.`bid` + THEN `za`.`price` + ELSE `za`.`bid` + END AS `price`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`claimed` = 0 + AND `za`.`sold` = 1 + AND `za`.`bidder_account_id` = {$this_account_id} + ORDER BY `p`.`level` desc + "); + //data_dump($pending, false, "Pending characters:"); + if ($pending !== false) { + ?> +

Congratulations!

+

You have 1) ? 'characters' : 'a character'; ?> ready to claim!

+ + + + + + + + + + + + + + + + + + + + +
LevelVocationDetailsPrice
VIEW
+ img + +

Hello master, what should my new name be?

+
+ + + + +
+
+ +

Ongoing auctions:

+ `za`.`bid` + THEN `za`.`price` + ELSE `za`.`bid`+{$step} + END AS `price`, + `za`.`time_begin`, + `za`.`time_end`, + `p`.`vocation`, + `p`.`level`, + `p`.`lookbody` AS `body`, + `p`.`lookfeet` AS `feet`, + `p`.`lookhead` AS `head`, + `p`.`looklegs` AS `legs`, + `p`.`looktype` AS `type`, + `p`.`lookaddons` AS `addons` + FROM `znote_auction_player` za + INNER JOIN `players` p + ON `za`.`player_id` = `p`.`id` + WHERE `p`.`account_id` = {$auction['storage_account_id']} + AND `za`.`sold` = 0 + ORDER BY `p`.`level` desc; + "); + //data_dump($characters, false, "List characters"); + if ($is_admin) { + ?> +

Admin: Character auction history

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
LevelVocationImageDetailsPriceAddedType
+ img + VIEW $character['time_end']) ? true : false; + echo getClock($character['time_begin'], true); + ?> + ('.toDuration(($character['time_end'] - time())).')'; ?>
+ +

Add a character to the auction.

+ = {$auction['lowestLevel']} + AND `a`.`points` >= $minToCreate + ;"); + //data_dump($own_characters, false, "own_chars"); + + if (is_array($own_characters) && !empty($own_characters)) { + $max = ($own_characters[0]['points'] / $auction['deposit']) * 100; + ?> +

Go back to list.

+
+ +

Character: (Must be offline)

+ +

Shop points: +
Your current points: +
Minimum: +
deposit: % +
Your maximum: +

+

Deposit information: +
To ensure you as the seller is a legitimate account, and to encourage fair prices you have to temporarily invest % of the selling price as a deposit. +

+

Once the auction has completed, the deposit fee will be refunded back to your account.

+

If you wish to reclaim your character, you can do it after the bidding period if nobody has placed an offer on it. But if you do this you will not get the deposit back. It is therefore advisable that you create a good and appealing offer to our community.

+

Sell price:

+ +
+

Verify with your password:

+ +
+ +
+ +

Go back to list.

+

Your account does not follow the required rules to sell characters. +
1. Minimum level: +
2. Minimum already earned shop points: +
3. Eligible characters must be offline. +

+ Character shop auctioning system is disabled.

"; - -include 'layout/overall/footer.php'; ?> - +include 'layout/overall/footer.php'; ?> \ No newline at end of file diff --git a/config.php b/config.php index 77fe255..353b4ef 100644 --- a/config.php +++ b/config.php @@ -926,15 +926,24 @@ ); ////////// - /// Let players sell characters. + /// Let players sell, buy and bid on characters. + /// Creates a deeper shop economy, encourages players to spend more money in shop for points. + /// Pay to win/progress mechanic, but also lets people who can barely afford points to gain it + /// by leveling characters to sell. It can also discourages illegal/risky third-party account + /// services. Since players can buy officially & support the server, dodgy competitors have to sell for cheaper. + /// Without admin interference this is organic to each individual community economy inflation. ///////// $config['shop_auction'] = array( 'characterAuction' => false, // Enable/disable this system - 'requiredLevel' => 50, // Minimum level of sold character - 'leastValue' => 10, // Lowest donation points a char can be sold for. - 'leastTime' => 24, // In hours. False to disable. - // leastTime = Lowest duration of time an auctioned player has to be - // sellable before auctioneer can claim character back. + // Account ID of the account that stores players in the auction. + // Make sure storage account has a very secure password! + 'storage_account_id' => 5, // Separate secure account ID, not your GM. + 'step' => 5, // Minimum amount someone can raise a bid by + 'step_duration' => 1 * 60 * 60, // When bidding over someone else, extend bid period by 1 hour. + 'lowestLevel' => 20, // Minimum level of sold character + 'lowestPrice' => 10, // Lowest donation points a char can be sold for. + 'biddingDuration' => 1 * 24 * 60 * 60, // = 1 day, 0 to disable bidding + 'deposit' => 10 // Seller has to add 10=10% deposit to auction which he gets back later. ); /* diff --git a/engine/database/connect.php b/engine/database/connect.php index 61e1c86..93b1b17 100644 --- a/engine/database/connect.php +++ b/engine/database/connect.php @@ -250,6 +250,21 @@ CREATE TABLE IF NOT EXISTS `znote_global_storage` ( UNIQUE (`key`) ) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS `znote_auction_player` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `player_id` int(11) NOT NULL, + `original_account_id` int(11) NOT NULL, + `bidder_account_id` int(11) NOT NULL, + `time_begin` int(11) NOT NULL, + `time_end` int(11) NOT NULL, + `price` int(11) NOT NULL, + `bid` int(11) NOT NULL, + `deposit` int(11) NOT NULL, + `sold` tinyint(1) NOT NULL, + `claimed` tinyint(1) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + INSERT INTO `znote` (`version`, `installed`) VALUES ('$version', '$time'); diff --git a/layout/menu.php b/layout/menu.php index ef10066..49be816 100644 --- a/layout/menu.php +++ b/layout/menu.php @@ -22,6 +22,9 @@
  • Guilds diff --git a/layout/widgets/Wadmin.php b/layout/widgets/Wadmin.php index a5d808f..3a1cfa9 100644 --- a/layout/widgets/Wadmin.php +++ b/layout/widgets/Wadmin.php @@ -23,6 +23,9 @@
  • Admin Shop
  • +
  • + Admin Auction +