1<?php require_once 'engine/init.php';
2znote_csrf_protect_public_post();
3theme_open();
4if ($config['log_ip']) {
5 znote_visitor_insert_detailed_data(3);
6}
7
8$house = (isset($_GET['id']) && (int)$_GET['id'] > 0) ? (int)$_GET['id'] : false;
9$house_SQL = "";
10$house_SQL_params = [];
11if ($house !== false) {
12 $house_SQL = "
13 SELECT
14 `h`.`id`, `h`.`owner`, `h`.`paid`, `h`.`name`, `h`.`rent`, `h`.`town_id`,
15 `h`.`size`, `h`.`beds`, " . houseSelect(array('bid','bid_end','last_bid','highest_bidder'), 'h') . ",
16 `p`.`name` AS `ownername`
17 FROM `houses` AS `h`
18 LEFT JOIN `players` AS `p`
19 ON `h`.`owner` > 0
20 AND `p`.`id` = `h`.`owner`
21 WHERE `h`.`id` = ?;
22 ";
23 $house_SQL_params = [$house];
24 $house = db()->fetchOne($house_SQL, $house_SQL_params);
25 if (!is_array($house)) {
26 ?>
27 <h1><?= t('house.not_found') ?></h1>
28 <p><?= t('house.go_back') ?> <a href="houses.php">house list</a> and select a house for further details.</p>
29 <?php
30 theme_close();
31 exit;
32 }
33 $minbid = $config['houseConfig']['minimumBidSQM'] * $house['size'];
34 if ($house['owner'] == 0) unset($house['ownername']);
35
36 if ($config['houseConfig']['shopPoints']['enabled']) {
37 $house['points'] = $house['size'];
38
39 foreach ($config['houseConfig']['shopPoints']['cost'] AS $cost_sqm => $cost_points) {
40 if ($cost_sqm < $house['size']) $house['points'] = $cost_points;
41 }
42 }
43
44 //data_dump($house, false, "House data");
45
46 //////////////////////
47 // Bid on house logic
48 $bid_char = &$_POST['char'];
49 $bid_amount = &$_POST['amount'];
50 if ($bid_amount && $bid_char) {
51 $bid_char = (int)$bid_char;
52 $bid_amount = (int)$bid_amount;
53
54 $player = db()->fetchOne("
55 SELECT `id`, `account_id`, `name`, `level`, `balance`
56 FROM `players`
57 WHERE `id` = ? LIMIT 1;
58 ", [$bid_char]);
59
60 if (user_logged_in() === true && is_array($player) && $player['account_id'] == $session_user_id) {
61 // Does player have or need premium?
62 $premstatus = ($config['houseConfig']['requirePremium'] && $user_data['premdays'] == 0) ? false : true;
63 if ($premstatus) {
64
65 // Can player have or bid on more houses?
66 $pHouseCount = db()->fetchOne("
67 SELECT COUNT('id') AS `value`
68 FROM `houses`
69 WHERE (
70 (`" . houseCol('highest_bidder') . "` = ? AND `owner` = ?)
71 OR (`" . houseCol('highest_bidder') . "` = ?)
72 OR (`owner` = ?)
73 )
74 AND `id` != ? LIMIT 1;
75 ", [$bid_char, $bid_char, $bid_char, $bid_char, $house['id']]);
76
77 if ($pHouseCount['value'] < $config['houseConfig']['housesPerPlayer']) {
78 // Is character level high enough?
79 if ($player['level'] >= $config['houseConfig']['levelToBuyHouse']) {
80 // Can player afford this bid?
81 if ($player['balance'] > $bid_amount) {
82
83 // Is bid higher than previous bid?
84 if ($bid_amount > $house['bid']) {
85 // Is bid higher than lowest bid?
86 if ($bid_amount > $minbid) {
87 // Should only apply to external players, allowing a player to up his pledge without
88 // being forced to pay his full previous bid.
89 if ($house['highest_bidder'] != $player['id']) $lastbid = $house['bid'] + 1;
90 else {
91 $lastbid = $house['last_bid'];
92 echo "<b><font color='green'>You have raised the house pledge to ".$bid_amount."gp!</font></b><br>";
93 }
94 // Has bid already started?
95 if ($house['bid_end'] > 0) {
96 if ($house['bid_end'] > time()) {
97
98 db()->execute("
99 UPDATE `houses`
100 SET
101 `" . houseCol('highest_bidder') . "` = ?,
102 `" . houseCol('bid') . "` = ?,
103 `" . houseCol('last_bid') . "` = ?
104 WHERE `id` = ? LIMIT 1;
105 ", [$player['id'], $bid_amount, $lastbid, $house['id']]);
106
107 $house = db()->fetchOne("
108 SELECT
109 `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`,
110 `beds`, " . houseSelect(array('bid','bid_end','last_bid','highest_bidder')) . "
111 FROM `houses`
112 WHERE `id` = ?;
113 ", [$house['id']]);
114 }
115
116 } else {
117 $lastbid = $minbid + 1;
118 $bidend = time() + $config['houseConfig']['auctionPeriod'];
119
120 db()->execute("
121 UPDATE `houses`
122 SET
123 `" . houseCol('highest_bidder') . "` = ?,
124 `" . houseCol('bid') . "` = ?,
125 `" . houseCol('last_bid') . "` = ?,
126 `" . houseCol('bid_end') . "` = ?
127 WHERE `id` = ? LIMIT 1;
128 ", [$player['id'], $bid_amount, $lastbid, $bidend, $house['id']]);
129
130 $house = db()->fetchOne("
131 SELECT
132 `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`,
133 `beds`, " . houseSelect(array('bid','bid_end','last_bid','highest_bidder')) . "
134 FROM `houses`
135 WHERE `id` = ?;
136 ", [$house['id']]);
137
138 }
139 echo "<b><font color='green'>". t('house.highest_bid') ."</font></b>";
140 } else echo "<b><font color='red'>You need to place a bid that is higher or equal to {$minbid}gp.</font></b>";
141
142 } else {
143 // Check if current bid is higher than last_bid
144 if ($bid_amount > $house['last_bid']) {
145 // Should only apply to external players, allowing a player to up his pledge without
146 // being forced to pay his full previous bid.
147 if ($house['highest_bidder'] != $player['id']) {
148 $lastbid = $bid_amount + 1;
149
150 db()->execute("
151 UPDATE `houses`
152 SET `" . houseCol('last_bid') . "` = ?
153 WHERE `id` = ? LIMIT 1;
154 ", [$lastbid, $house['id']]);
155
156 $house = db()->fetchOne("
157 SELECT
158 `id`, `owner`, `paid`, `name`, `rent`, `town_id`, `size`,
159 `beds`, " . houseSelect(array('bid','bid_end','last_bid','highest_bidder')) . "
160 FROM `houses`
161 WHERE `id` = ?;
162 ", [$house['id']]);
163
164 echo "<b><font color='orange'>Unfortunately your bid was not higher than previous bidder.</font></b>";
165 } else {
166 echo "<b><font color='orange'>". t('house.already_higher') ."</font></b>";
167 }
168 } else {
169 echo "<b><font color='red'>" . t('house.bid_too_low') . "</font></b>";
170 }
171 }
172 } else echo "<b><font color='red'>" . t('house.not_enough2') . "</font></b>";
173 } else echo "<b><font color='red'>" . t('house.level_too_low', ['level' => $config['houseConfig']['levelToBuyHouse'] - 1]) . "</font></b>";
174 } else echo "<b><font color='red'>". t('house.too_many') ."</font></b>";
175 } else echo "<b><font color='red'>" . t('house.need_premium2') . "</font></b>";
176 } else echo "<b><font color='red'>" . t('house.own_char_only') . "</font></b>";
177 }
178
179 ////////////////////////////////////////
180 // Instantly buy house with shop points
181 if ($config['houseConfig']['shopPoints']['enabled']
182 && isset($_POST['instantbuy'])
183 && $bid_char
184 && $house['owner'] == 0
185 && isset($house['points'])) {
186
187 $account_points = (int)$user_znote_data['points'];
188
189 if ($account_points >= $house['points']) {
190
191 $bid_char = (int)$bid_char;
192 $player = db()->fetchOne("
193 SELECT `id`, `account_id`, `name`, `level`
194 FROM `players`
195 WHERE `id` = ? LIMIT 1;
196 ", [$bid_char]);
197
198 $pHouseCount = db()->fetchOne("
199 SELECT COUNT('id') AS `value`
200 FROM `houses`
201 WHERE (
202 (`" . houseCol('highest_bidder') . "` = ? AND `owner` = ?)
203 OR (`" . houseCol('highest_bidder') . "` = ?)
204 OR (`owner` = ?)
205 )
206 AND `id` != ? LIMIT 1;
207 ", [$bid_char, $bid_char, $bid_char, $bid_char, $house['id']]);
208
209 if (user_logged_in() === true
210 && $player['account_id'] == $session_user_id
211 && $player['level'] >= $config['houseConfig']['levelToBuyHouse']
212 && $pHouseCount['value'] < $config['houseConfig']['housesPerPlayer']) {
213
214 $house_points = (int)$house['points'];
215 $house_id = $house['id'];
216 $time = time();
217
218 // Lock the account balance and the house row together, so two
219 // concurrent purchases (or a purchase racing a bid) cannot both
220 // succeed or spend points that were already spent.
221 $purchased = db()->transaction(function ($db) use ($session_user_id, $bid_char, $house_id, $house_points, $time) {
222 $account = $db->fetchOne("SELECT `points` FROM `znote_accounts` WHERE `account_id` = ? LIMIT 1 FOR UPDATE;", [$session_user_id]);
223 if (!is_array($account) || (int)$account['points'] < $house_points) {
224 return false;
225 }
226
227 $houseRow = $db->fetchOne("SELECT `owner` FROM `houses` WHERE `id` = ? LIMIT 1 FOR UPDATE;", [$house_id]);
228 if (!is_array($houseRow) || (int)$houseRow['owner'] !== 0) {
229 return false;
230 }
231
232 $db->execute("UPDATE `znote_accounts` SET `points` = `points` - ? WHERE `account_id` = ? LIMIT 1;", [$house_points, $session_user_id]);
233 $db->execute("UPDATE `houses` SET `owner` = ? WHERE `id` = ? LIMIT 1;", [$bid_char, $house_id]);
234 $db->execute("
235 INSERT INTO `znote_shop_logs`
236 (`account_id`, `player_id`, `type`, `itemid`, `count`, `points`, `time`) VALUES
237 (?, ?, 7, ?, 1, ?, ?)
238 ", [$session_user_id, $bid_char, $house_id, $house_points, $time]);
239 $db->execute("
240 INSERT INTO `znote_shop_orders`
241 (`account_id`, `type`, `itemid`, `count`, `time`) VALUES
242 (?, 7, ?, ?, ?)
243 ", [$session_user_id, $house_id, $bid_char, $time]);
244
245 return true;
246 });
247
248 if ($purchased) {
249 // Reload house data
250 $house = db()->fetchOne($house_SQL, $house_SQL_params);
251 $minbid = $config['houseConfig']['minimumBidSQM'] * $house['size'];
252 if ($house['owner'] > 0) $house['ownername'] = user_name($house['owner']);
253
254 // Congratulate user and tell them they still has to pay rent (if rent > 0)
255 ?>
256 <p><strong><?= t('house.congrats') ?></strong>
257 <br>You now own this house!
258 <br><?= t('house.remember_say') ?> <strong>!shop</strong> in-game to process your ownership!
259 <?php if ($house['rent'] > 0): ?>
260 <br>Keep in mind you still need to pay rent on this house, make sure you have enough bank balance to cover it!
261 <?php endif; ?>
262 </p>
263 <?php
264 } else {
265 ?>
266 <p><strong>Error:</strong>
267 <br>This house was already bought or your points balance changed. Please refresh and try again.
268 </p>
269 <?php
270 }
271 } else {
272 ?>
273 <p><strong>Error:</strong>
274 <br>Either your level is too low, or your player already have or is bidding on another house.
275 <br><?= t('house.your_level') ?> <?php echo $player['level']; ?>. Minimum level to buy house: <?php echo $config['houseConfig']['levelToBuyHouse']; ?>
276 <br><?= t('house.your_bids') ?> <?php echo $pHouseCount['value']; ?>. Maximum house per player: <?php echo $config['houseConfig']['housesPerPlayer']; ?>.
277 </p>
278 <?php
279 }
280 }
281 }
282
283 // HTML structure and logic
284 ?>
285 <h1><?= t('house.label') ?> <?php echo $house['name']; ?></h1>
286 <ul>
287 <li><b>Town</b>:
288 <?php
289 $town_name = &$config['towns'][$house['town_id']];
290 echo "<a href='houses.php?id={$house['town_id']}'>". ($town_name ? $town_name : 'Specify town id ' . $house['town_id'] . ' name in config.php first.') ."</a>";
291 ?></li>
292 <li><b>Size</b>: <?php echo $house['size']; ?></li>
293 <li><b>Beds</b>: <?php echo $house['beds']; ?></li>
294 <li><b>Owner</b>: <?php
295 if ($house['owner'] > 0) echo "<a href='characterprofile.php?name={$house['ownername']}' target='_BLANK'>{$house['ownername']}</a>";
296 else echo "Available for auction.";
297 ?></li>
298 <li><b>Rent</b>: <?php echo $house['rent']; ?></li>
299 <?php if ($house['owner'] == 0 && isset($house['points'])): ?>
300 <li><b><?= t('house.shop_points2') ?></b>: <?php echo $house['points']; ?></li>
301 <?php endif; ?>
302 </ul>
303 <?php
304 // AUCTION MARKUP INIT
305 if ($house['owner'] == 0) {
306 ?>
307 <h2><?= t('house.on_auction2') ?></h2>
308 <?php
309 if ($house['highest_bidder'] == 0) echo "<b>" . t('house.no_bidders2') . "</b>";
310 else {
311 $bidder = db()->fetchOne("SELECT `name` FROM `players` WHERE `id` = ? LIMIT 1;", [$house['highest_bidder']]);
312 echo "<b>" . t('house.has_bidders') . "</b>";
313 echo "<br><b>" . t('house.active_bid') . "</b> {$house['last_bid']}gp";
314 echo "<br><b>" . t('house.active_bid_by') . "</b> <a href=\"characterprofile.php?name=" . htmlspecialchars((string)$bidder['name'], ENT_QUOTES, 'UTF-8') . "\" target=\"_BLANK\">" . htmlspecialchars((string)$bidder['name'], ENT_QUOTES, 'UTF-8') . "</a>";
315 echo "<br><b>" . t('house.bid_ends') . "</b> ". getClock($house['bid_end'], true);
316 }
317
318 if ($house['bid_end'] == 0 || $house['bid_end'] > time()) {
319 if (user_logged_in()) {
320 // Your characters, indexed by char_id
321 $yourChars = db()->fetchAll("SELECT `id`, `name`, `balance` FROM `players` WHERE `account_id` = ?;", [$user_data['id']]);
322 if ($yourChars !== false) {
323 $charData = array();
324 foreach ($yourChars as $char) {
325 $charData[$char['id']] = $char;
326 }
327 ?>
328 <form class="house_form_bid" action="" method="post">
329 <select name="char">
330 <?php
331 foreach ($charData as $id => $char) {
332 echo "<option value='$id'>{$char['name']} [{$char['balance']}]</option>";
333 }
334 ?>
335 </select>
336 <input type="text" name="amount" placeholder="<?= t('house.min_bid', ['amount' => $minbid + 1]) ?>">
337 <input type="submit" value="<?= t('house.bid_submit') ?>">
338 </form>
339 <?php if ($house['owner'] == 0 && isset($house['points'])): ?>
340 <br>
341 <?php if ((int)$user_znote_data['points'] >= $house['points']): ?>
342 <form class="house_form_buy" action="" method="post">
343 <p><?= t('house.your_account') ?> <strong><?php echo $user_znote_data['points']; ?></strong> available shop points.</p>
344 <select name="char">
345 <?php
346 foreach ($charData as $id => $char) {
347 echo "<option value='$id'>". $char['name'] ."</option>";
348 }
349 ?>
350 </select>
351 <input type="submit" name="instantbuy" value="Buy now for <?php echo $house['points']; ?> shop points!">
352 </form>
353 <?php else: ?>
354 <p><?= t('house.your_account') ?> <strong><?php echo $user_znote_data['points']; ?></strong> available shop points.
355 <br>You don't have enough shop points to instantly buy this house.</p>
356 <?php endif; ?>
357 <?php endif; ?>
358 <?php
359 } else echo "<br>You need a character to bid on this house.";
360 } else echo "<br>You need to login before you can bid on houses.";
361 } else echo "<br><b>Bid has ended! House transaction will proceed next server restart assuming active bidder have sufficient balance.</b>";
362 }
363} else {
364 ?>
365 <h1><?= t('house.none_selected') ?></h1>
366 <p><?= t('house.go_back') ?> <a href="houses.php">house list</a> and select a house for further details.</p>
367 <?php
368}
369theme_close(); ?>
370