/
BuildingsQueue.sol
595 lines (510 loc) · 22 KB
/
BuildingsQueue.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
pragma solidity ^0.4.23;
import 'zeppelin-solidity/contracts/math/SafeMath.sol';
import 'zeppelin-solidity/contracts/ownership/NoOwner.sol';
import './AssetsRequirements.sol';
import './BuildingsData.sol';
import './UserBuildings.sol';
import './UserResources.sol';
import './Versioned.sol';
/*
* @title BuildingsQueue (WIP)
* @dev Issue: * https://github.com/e11-io/crypto-wars-solidity/issues/4
*/
contract BuildingsQueue is NoOwner, Versioned {
using SafeMath for uint;
/*
* @dev Event for Adding a building to the queue system logging.
* @param _user who receives the building.
* @param _id the id of the building to be added.
* @param _index the index of the building to be added.
* @param _startBlock the number of the block where the construction is gonna start.
* @param _endBlock the number of the block where the construction is gonna be ready.
*/
event AddNewBuildingToQueue(address _user,
uint _id,
uint _index,
uint _startBlock,
uint _endBlock);
/*
* @dev Event for Updating the buildings queue. Buildings that are ready will be
* tranfered to the user buildings contract, and removed from the queue.
* @param _user the address of the user's queue to be updated. (address)
* @param _ids The ids of the finished buldings. (uint[])
* @param _indexes The indexes of the finished buldings. (uint[])
*/
event UpdateQueue(address _user,
uint[] _ids,
uint[] _indexes);
/*
* @dev Event for upgrading a building to the queue system logging.
* @param _idOfUpgrade the id of the building to be added.
* @param _index the index of the building to be added.
* @param _startBlock the number of the block where the construction is gonna start.
* @param _endBlock the number of the block where the construction is gonna be ready.
*/
event UpgradeBuilding(uint _idOfUpgrade, uint _index, uint startBlock, uint endBlock);
/*
*
*/
event RemoveBuilding(address _user, uint _id, uint _index);
struct Build {
uint32 id;
uint32 index;
uint64 startBlock;
uint64 endBlock;
}
AssetsRequirements assetsRequirements;
BuildingsData buildingsData;
BuildingsQueue previousBuildingsQueue;
UserBuildings userBuildings;
UserResources userResources;
// Mapping of user -> build struct array (keeps track of buildings in construction queue)
mapping (address => Build[]) public userBuildingsQueue;
/*
* @notice Constructor: Instantiate Buildings Queue contract.
* @dev Constructor function to provide User Buildings address and instantiate it.
*/
constructor() public {
}
/*
* @notice Makes the contract type verifiable.
* @dev Function to prove the contract is Buildings Queue.
*/
function isBuildingsQueue() external pure returns (bool) {
return true;
}
/*
* @notice Sets the contract's version and instantiates the previous version contract.
* @dev Function to set the contract version and instantiate the previous Buildings Queue.
* @param _previousBuildingsQueue the address of previous Buildings Queue contract. (address)
* @param _version the current contract version number. (uint)
*/
function setBuildingsQueueVersion(BuildingsQueue _previousBuildingsQueue, uint _version) external onlyOwner {
require(_previousBuildingsQueue.isBuildingsQueue());
require(_version > _previousBuildingsQueue.version());
previousBuildingsQueue =_previousBuildingsQueue;
setVersion(_version);
}
/*
* @title Instantiate User Buildings contract.
* @dev Function to provide User Buildings address and instantiate it.
* @param _userBuildings the address of User Buildings contract. (address)
*/
function setUserBuildings(UserBuildings _userBuildings) external onlyOwner {
require(_userBuildings.isUserBuildings());
userBuildings = _userBuildings;
}
/*
* @title Instantiate Buildings Data contract.
* @dev Function to provide Buildings Data address and instantiate it.
* @param _buildingsData the address of Buildings Data contract. (address)
*/
function setBuildingsData(BuildingsData _buildingsData) external onlyOwner {
require(_buildingsData.isBuildingsData());
buildingsData = _buildingsData;
}
/*
* @title Instantiate User Resources contract.
* @dev Function to provide User Resources address and instantiate it.
* @param _userResources the address of User Resources contract. (address)
*/
function setUserResources(UserResources _userResources) external onlyOwner {
require(_userResources.isUserResources());
userResources = _userResources;
}
/*
* @title Instantiate Assets Requirements contract.
* @dev Function to provide Assets Requirements address and instantiate it.
* @param _assetsRequirements the address of Assets Requirements contract. (address)
*/
function setAssetsRequirements(AssetsRequirements _assetsRequirements) external onlyOwner {
require(_assetsRequirements.isAssetsRequirements());
assetsRequirements = _assetsRequirements;
}
/*
* @notice Add New Building To Queue.
* @dev Function to add a new building to the construction queue of the user.
index -2: building type is not unique and the builiding cant be created.
index -1: building type is unique and the building can be created. Must be initialized.
index >= 0: building was deleted and must be created again. Modifing it's active variable to true.
* @param _id of the building to add to the queue. (uint)
*/
function addNewBuildingToQueue(uint _id) external {
require(buildingsData.checkBuildingExist(_id));
require(assetsRequirements.validateUserAssetRequirements(msg.sender, _id));
uint typeId = buildingsData.getBuildingTypeId(_id);
int index = userBuildings.buildingTypeIsUnique(msg.sender, typeId, _id);
require(index > -2);
uint price;
uint resourceType;
uint blocks;
(price, resourceType, blocks) = buildingsData.getBuildingData(_id);
consumeResources(msg.sender, price, resourceType);
if (index == -1) {
index = int(userBuildings.initNewBuilding(msg.sender, _id));
}
uint startBlock = getStartBlock(msg.sender);
uint endBlock = startBlock.add(blocks);
userBuildingsQueue[msg.sender].push(Build(
uint32(_id),
uint32(uint(index)),
uint64(startBlock),
uint64(endBlock)
));
emit AddNewBuildingToQueue(msg.sender,
_id,
uint(index),
startBlock,
endBlock);
}
/*
* @title Update Queue.
* @dev Function to update the user buildings queue. Finished buildings
* will be send to the User Buildings contract and removed from the construction
* queue. Queue will be resized and updated with only the unfinished buildings.
* @param _user the address of the user's queue to be updated. (address)
* @return an array of the finished buildings ids. (uint[])
*/
function updateQueue(address _user) public {
require(_user != address(0));
if (userBuildingsQueue[_user].length == 0) {
return;
}
uint length = userBuildingsQueue[_user].length;
uint i = 0;
while (i < length && userBuildingsQueue[_user][i].endBlock < block.number) {
i++;
}
uint[] memory finishedIds = new uint[](i);
uint[] memory finishedIndexes = new uint[](i);
if (i == 0) {
emit UpdateQueue(_user, finishedIds, finishedIndexes);
return;
}
for (uint j = 0; j < i; j++) {
Build storage building = userBuildingsQueue[_user][j];
finishedIds[j] = building.id;
finishedIndexes[j] = building.index;
}
require(userBuildings.addUserBuildings(_user, finishedIds, finishedIndexes));
require(shiftUserBuildings(_user, i));
emit UpdateQueue(_user, finishedIds, finishedIndexes);
}
/*
* @title Upgrade Building.
* @dev Function to upgrade a buildings. If the building is in queue
* the queue is updated.
* @param _id The id of the building that is gonna be replaced for the upgrade. (uint)
* @param _idOfUpgrade The id of the upgrade. (uint)
* @param _index The index of the building. (uint)
*/
function upgradeBuilding(uint _id, uint _idOfUpgrade, uint _index) external {
require(buildingsData.checkBuildingExist(_id));
require(buildingsData.checkBuildingExist(_idOfUpgrade));
require(buildingsData.checkUpgrade(_id, _idOfUpgrade));
require(assetsRequirements.validateUserAssetRequirements(msg.sender, _idOfUpgrade));
bool buildingIsInQueue;
uint buildingIndexInQueue;
(buildingIsInQueue, buildingIndexInQueue) = findBuildingInQueue(msg.sender, _id, _index);
uint price;
uint resourceType;
uint blocks;
(price, resourceType, blocks) = buildingsData.getBuildingData(_idOfUpgrade);
consumeResources(msg.sender, price, resourceType);
if (buildingIsInQueue) {
require(userBuildingsQueue[msg.sender][buildingIndexInQueue].endBlock < block.number);
updateQueue(msg.sender);
}
require(userBuildings.upgradeBuilding(msg.sender, _id, _index));
uint startBlock = getStartBlock(msg.sender);
uint endBlock = startBlock.add(blocks);
userBuildingsQueue[msg.sender].push(Build(
uint32(_idOfUpgrade), uint32(_index), uint64(startBlock), uint64(endBlock)
));
emit UpgradeBuilding(_idOfUpgrade, _index, startBlock, endBlock);
}
/*
* @title Find Building In Queue.
* @dev Function to find a building in queue.
* @param _user The address of the user. (address)
* @param _id The id of the building. (uint)
* @param _index The Index of the building. (uint)
* @return A boolean representing if the building exists in the queue
* and a uint representing the index/position of the building in the queue.
*/
function findBuildingInQueue(address _user, uint _id, uint _index) public view returns (bool exists, uint indexInQueue) {
for (uint i = 0; i < userBuildingsQueue[_user].length; i++) {
if (_index == userBuildingsQueue[_user][i].index && _id == userBuildingsQueue[_user][i].id) {
return (true, i);
}
}
return (false, 0);
}
/*
* @title Update Queue Blocks.
* @dev Function to update the start and end blocks of each building in queue
* when a building is removed.
* @param _user The address of the user's queue to be updated. (address)
* @param _queueIndex The index in queue of the building. (uint)
*/
function updateQueueBlocks(address _user, uint _queueIndex) internal {
uint64 blocks;
for (uint i = _queueIndex + 1; i < userBuildingsQueue[_user].length; i++) {
/* TODO: might need to implement safe math later*/
blocks = userBuildingsQueue[_user][i].endBlock - userBuildingsQueue[_user][i].startBlock;
if (i == _queueIndex + 1) {
if (userBuildingsQueue[_user][_queueIndex].startBlock >= block.number) {
userBuildingsQueue[_user][i].startBlock = userBuildingsQueue[_user][i - 1].startBlock;
}
if (userBuildingsQueue[_user][_queueIndex].startBlock < block.number) {
userBuildingsQueue[_user][i].startBlock = uint64(block.number);
}
userBuildingsQueue[_user][i].endBlock = userBuildingsQueue[_user][i].startBlock + blocks;
} else {
userBuildingsQueue[_user][i].startBlock = userBuildingsQueue[_user][i - 1].endBlock;
userBuildingsQueue[_user][i].endBlock = userBuildingsQueue[_user][i].startBlock + blocks;
}
}
}
/*
* @notice Shift User Buildings.
* @dev Function to shift the specified amount of builds from user buildings queue.
* Being a chronologically ordered array, finished buildings will be always
* first on the array.
* @param _user the address of the user's queue to be updated. (address)
* @param _amount of buildings to be shifted from the array. (uint)
* @return A boolean that indicates if the operation was successful.
*/
function shiftUserBuildings(address _user, uint _amount) internal returns (bool) {
require(_amount <= userBuildingsQueue[_user].length);
for (uint i = _amount; i < userBuildingsQueue[_user].length; i++){
userBuildingsQueue[_user][i - _amount] = userBuildingsQueue[_user][i];
}
for (uint j = 1; j <= _amount; j++){
delete userBuildingsQueue[_user][userBuildingsQueue[_user].length - j];
}
userBuildingsQueue[_user].length -= _amount;
return true;
}
/*
* @notice Shift One User Buildings.
* @dev Function to shift one specified building from user buildings queue.
* @param _user The address of the user's queue to be updated. (address)
* @param _index The index of the building to be removed. (uint)
* @return A boolean that indicates if the operation was successful.
*/
function shiftOneUserBuilding(address _user, uint _index) internal returns (bool) {
for (uint i = _index; i < userBuildingsQueue[_user].length; i++) {
if (i == userBuildingsQueue[_user].length - 1) {
delete userBuildingsQueue[_user][userBuildingsQueue[_user].length - 1];
} else {
userBuildingsQueue[_user][i] = userBuildingsQueue[_user][i + 1];
}
}
userBuildingsQueue[_user].length -= 1;
return true;
}
/*
* @notice Get the last building in the construction queue of the user.
* @param _user . (address)
*/
function getLastUserBuilding(address _user) external view returns (uint id,
uint startBlock,
uint endBlock) {
require(_user != address(0));
require(userBuildingsQueue[_user].length > 0);
Build storage b = userBuildingsQueue[_user][userBuildingsQueue[_user].length -1];
return (b.id, b.startBlock, b.endBlock);
}
/*
* @notice Check Building Price and Get Block of it.
* @dev Function to check if the user has the necessary amount of resources
* to build/upgrade the building. And returns the amount of blocks that
* building takes to be done.
* @param _user the address of the user. (address)
* @param _price the price for the upgrade. (uint)
* @param _resourceType the type of the resource to consume. (uint)
* @return returns the amount of blocks that building takes to be done.
*/
function consumeResources(address _user, uint _price, uint _resourceType) internal {
require(_resourceType == 0 || _resourceType == 1 || _resourceType == 2);
if (_price > 0) {
if (_resourceType == 0) {
require(userResources.consumeGold(_user, _price));
}
if (_resourceType == 1) {
require(userResources.consumeCrystal(_user, _price));
}
if (_resourceType == 2) {
require(userResources.consumeQuantumDust(_user, _price));
}
}
}
/*
* @notice Get Start Block.
* @dev Function to get the start block number of the new building to be
* added in the queue.
* @param _user the address of the user (address)
*/
function getStartBlock(address _user) internal view returns (uint) {
uint length = userBuildingsQueue[_user].length;
uint startBlock = block.number;
if (length > 0) {
Build storage lastBuilding = userBuildingsQueue[_user][SafeMath.sub(length, 1)];
if (block.number < lastBuilding.endBlock ) {
startBlock = lastBuilding.endBlock;
}
}
return startBlock;
}
/*
* @notice Get Buildings in Queue.
* @dev Function to check the ids of all the user's buildings in queue.
* @param _user the address of the user's queue to be shown. (address)
*/
function getBuildingsInQueue(address _user) external view returns (uint[]) {
require(_user != address(0));
uint length = userBuildingsQueue[_user].length;
uint[] memory buildings = new uint[](length);
for (uint i = 0; i < length; i++) {
buildings[i] = userBuildingsQueue[_user][i].id;
}
return buildings;
}
/*
* @notice Get Ids and Blocks.
* @dev Function to get the ids, start block and end block of all buildings in queue.
* Used only for testing.
* @param _user the address of the user. (address)
* @return four uint arrays representiong the id, startBlock, endBlock and index of each building.
*/
function getBuildings(address _user) external view returns (uint[], uint[], uint[], uint[]) {
require(_user != address(0));
uint length = userBuildingsQueue[_user].length;
uint[] memory ids = new uint[](length);
uint[] memory startBlocks = new uint[](length);
uint[] memory endBlocks = new uint[](length);
uint[] memory indexes = new uint[](length);
for (uint i = 0; i < length; i++) {
ids[i] = userBuildingsQueue[_user][i].id;
startBlocks[i] = userBuildingsQueue[_user][i].startBlock;
endBlocks[i] = userBuildingsQueue[_user][i].endBlock;
indexes[i] = userBuildingsQueue[_user][i].index;
}
return (ids, startBlocks, endBlocks, indexes);
}
/*
* @notice Get Building Id and Block.
* @dev Function to check the id and end block of a building in queue.
* @param _user The address of the user's queue to search in. (address)
* @param _index The index of the element to return in the array. (uint)
*/
function getBuildingIdAndEndBlock(address _user, uint _indexInQueue) external view returns (uint id, uint endBlock) {
require(userBuildingsQueue[_user].length > _indexInQueue);
return (userBuildingsQueue[_user][_indexInQueue].id, userBuildingsQueue[_user][_indexInQueue].endBlock);
}
/*
* @notice Get Building Index.
* @dev Function to get the id and index of a building.
* Used only for testing.
* @param _user The address of the user. (address)
* @param _indexInQueue The index in queue of the building. (uint)
* @return Two uints representiong the id and index of a building.
*/
function getBuildingIndex(address _user, uint _indexInQueue) external view returns (uint id, uint index) {
require(_user != address(0));
require(_indexInQueue < userBuildingsQueue[_user].length);
return (userBuildingsQueue[_user][_indexInQueue].id, userBuildingsQueue[_user][_indexInQueue].index);
}
/*
* @notice Get User Queue Resources.
* @dev Function to get the amount of gold and crystal produced in the buildings Queue
* since the last payout to the user.
* @param _user The address of the user to give the resources. (address)
* @param _payoutBlock The block where was the last payout to the user. (uint)
* @return Two uints representing the gold and crystal produced in the queue system.
*/
function getUserQueueResources(address _user) external view returns (uint queueGold, uint queueCrystal) {
uint goldRate;
uint crystalRate;
uint diff;
uint payoutBlock = userResources.usersPayoutBlock(_user);
for (uint i = 0; i < userBuildingsQueue[_user].length; i++) {
if (userBuildingsQueue[_user][i].endBlock < block.number) {
(goldRate, crystalRate) = buildingsData.getGoldAndCrystalRates(userBuildingsQueue[_user][i].id);
if (userBuildingsQueue[_user][i].endBlock > payoutBlock) {
diff = SafeMath.sub(block.number, userBuildingsQueue[_user][i].endBlock);
} else {
diff = SafeMath.sub(block.number, payoutBlock);
}
if (diff > 0) {
if (goldRate > 0) {
queueGold = SafeMath.add(queueGold, SafeMath.mul(goldRate, diff));
}
if (crystalRate > 0) {
queueCrystal = SafeMath.add(queueCrystal, SafeMath.mul(crystalRate, diff));
}
}
}
}
return (queueGold, queueCrystal);
}
/*
* @title Get Buildings Queue Length
* @dev Get the length of the buildings Queue.
* @param _user The address of the user. (address)
*/
function getBuildingsQueueLength(address _user) external view returns (uint length) {
return userBuildingsQueue[_user].length;
}
/*
* @title Cancel Building.
* @dev Function to cancel/remove building from queue. The building is set
* to Active false in User Buildings.
* @param _id The id of the building to be removed. (uint)
* @param _index The Index of the building to be removed. (uint)
*/
function cancelBuilding(uint _id, uint _index) external {
require(buildingsData.checkBuildingExist(_id));
bool buildingIsInQueue;
uint buildingIndexInQueue;
(buildingIsInQueue, buildingIndexInQueue) = findBuildingInQueue(msg.sender, _id, _index);
require(buildingIsInQueue);
require(userBuildingsQueue[msg.sender][buildingIndexInQueue].endBlock > block.number);
userResources.payoutResources(msg.sender);
updateQueueBlocks(msg.sender, buildingIndexInQueue);
require(shiftOneUserBuilding(msg.sender, buildingIndexInQueue));
require(userBuildings.updateBuildingStatus(msg.sender, _id, _index));
emit RemoveBuilding(msg.sender, _id, _index);
}
/*
* @title Get User Resources Capacity.
* @dev Function to get the resources capacity of the buildings in queue.
* @param _user The address of the user. (address)
* @return Two uints representing the gold and crystal capacity of the buildings in queue.
*/
function getUserResourcesCapacity(address _user) external view returns (uint totalGoldCapacity, uint totalCrystalCapacity) {
uint goldCapacity;
uint crystalCapacity;
for (uint i = 0; i < userBuildingsQueue[_user].length; i++) {
uint buildingId = 0;
if (userBuildingsQueue[_user][i].endBlock < block.number) {
buildingId = userBuildingsQueue[_user][i].id;
}
if (userBuildingsQueue[_user][i].endBlock >= block.number &&
((userBuildingsQueue[_user][i].id - userBuildingsQueue[_user][i].id % 1000) / 1000) > 1) {
buildingId = userBuildingsQueue[_user][i].id - 1000;
}
if (buildingId > 0) {
(goldCapacity, crystalCapacity) = buildingsData.getGoldAndCrystalCapacity(buildingId);
if (goldCapacity > 0) {
totalGoldCapacity = SafeMath.add(totalGoldCapacity, goldCapacity);
}
if (crystalCapacity > 0) {
totalCrystalCapacity = SafeMath.add(totalCrystalCapacity, crystalCapacity);
}
}
}
return (totalGoldCapacity, totalCrystalCapacity);
}
}