527 lines
11 KiB
ActionScript
527 lines
11 KiB
ActionScript
import research;
|
|
import saving;
|
|
import unlock_tags;
|
|
import achievements;
|
|
import unlock_tags;
|
|
|
|
tidy class ResearchGrid : Component_ResearchGrid, Savable {
|
|
ReadWriteMutex mtx;
|
|
|
|
TechnologyGrid@ grid;
|
|
double researchRate = 0;
|
|
double points = 0;
|
|
double totalGenerated = 0;
|
|
|
|
bool delta = false;
|
|
bool gridDelta = false;
|
|
|
|
Mutex unlockMtx;
|
|
array<int> tagUnlocks;
|
|
bool unlockDelta = false;
|
|
|
|
double StatRecordDelay = 5.0;
|
|
|
|
ResearchGrid() {}
|
|
|
|
void save(SaveFile& file) {
|
|
file << researchRate;
|
|
file << points;
|
|
file << grid;
|
|
file << totalGenerated;
|
|
|
|
uint cnt = tagUnlocks.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
file.writeIdentifier(SI_UnlockTag, i);
|
|
file << tagUnlocks[i];
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
if(file < SV_0085) {
|
|
loadOld(file);
|
|
return;
|
|
}
|
|
file >> researchRate;
|
|
file >> points;
|
|
|
|
@grid = TechnologyGrid();
|
|
file >> grid;
|
|
file >> totalGenerated;
|
|
|
|
tagUnlocks.length = getUnlockTagCount();
|
|
for(uint i = 0, cnt = tagUnlocks.length; i < cnt; ++i)
|
|
tagUnlocks[i] = 0;
|
|
if(file >= SV_0091) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
int id = file.readIdentifier(SI_UnlockTag);
|
|
int val = 0;
|
|
file >> val;
|
|
|
|
if(id >= 0 && uint(id) < tagUnlocks.length)
|
|
tagUnlocks[id] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
double get_ResearchRate(Empire& emp) {
|
|
return researchRate * ResearchEfficiency * emp.ResearchGenerationFactor;
|
|
}
|
|
|
|
double get_ResearchPoints() {
|
|
return points;
|
|
}
|
|
|
|
double get_ResearchEfficiency() {
|
|
return 2000.0 / (2000.0 + totalGenerated);
|
|
}
|
|
|
|
void modResearchRate(double mod) {
|
|
WriteLock lock(mtx);
|
|
researchRate += mod;
|
|
}
|
|
|
|
bool gaveAchievement = false;
|
|
|
|
void researchTick(Empire& emp, double time) {
|
|
{
|
|
WriteLock lock(mtx);
|
|
double genPts = researchRate * time * ResearchEfficiency * emp.ResearchGenerationFactor;
|
|
totalGenerated += genPts;
|
|
points += genPts;
|
|
|
|
for(uint i = 0; i < grid.nodes.length; ++i) {
|
|
if(grid.nodes[i].timer >= 0)
|
|
delta = true;
|
|
grid.nodes[i].tick(emp, grid, time);
|
|
}
|
|
}
|
|
|
|
StatRecordDelay -= time;
|
|
bool recordStats = StatRecordDelay <= 0;
|
|
if(recordStats) {
|
|
emp.recordStat(stat::ResearchIncome, float(researchRate * ResearchEfficiency * emp.ResearchGenerationFactor));
|
|
emp.recordStat(stat::ResearchTotal, totalGenerated);
|
|
StatRecordDelay += 5.0;
|
|
|
|
if(!gaveAchievement && totalGenerated >= 25000.0) {
|
|
gaveAchievement = true;
|
|
giveAchievement(emp, "ACH_MAX_TECH");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void generatePoints(Empire& emp, double pts, bool modified = true, bool penalized = true) {
|
|
WriteLock lock(mtx);
|
|
double genPts = pts;
|
|
if(modified)
|
|
genPts *= ResearchEfficiency;
|
|
points += genPts;
|
|
if(penalized)
|
|
totalGenerated += genPts;
|
|
}
|
|
|
|
bool consumeResearchPoints(int amount) {
|
|
WriteLock lock(mtx);
|
|
if(points < amount)
|
|
return false;
|
|
points -= amount;
|
|
return true;
|
|
}
|
|
|
|
void freeResearchPoints(int amount) {
|
|
WriteLock lock(mtx);
|
|
points += amount;
|
|
}
|
|
|
|
void reduceResearchPenalty(int points) {
|
|
WriteLock lock(mtx);
|
|
totalGenerated = max(0.0, totalGenerated - points);
|
|
}
|
|
|
|
void initResearch(Empire& emp) {
|
|
WriteLock lock(mtx);
|
|
if(hasDLC("Heralds"))
|
|
@grid = getTechnologyGridSpec("Heralds").create();
|
|
else
|
|
@grid = getTechnologyGridSpec("Base").create();
|
|
tagUnlocks.length = getUnlockTagCount();
|
|
for(uint i = 0, cnt = tagUnlocks.length; i < cnt; ++i)
|
|
tagUnlocks[i] = 0;
|
|
|
|
//DLC unlock tags
|
|
for(uint i = 0, cnt = dlcs.length; i < cnt; ++i) {
|
|
if(!hasDLC(dlcs[i]))
|
|
continue;
|
|
int tag = getUnlockTag(dlcs[i]+"DLC");
|
|
tagUnlocks[tag] = 1;
|
|
}
|
|
|
|
//Pick some secret projects
|
|
for(uint n = 0; n < uint(config::PICK_SECRET_PROJECTS); ++n) {
|
|
TechnologyNode@ node;
|
|
double total = 0.0;
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
auto@ other = grid.nodes[i];
|
|
if(!other.secret || other.type.secretFrequency <= 0.0)
|
|
continue;
|
|
if(other.secretPicked)
|
|
continue;
|
|
if(!other.canBeSecret(emp))
|
|
continue;
|
|
|
|
total += other.type.secretFrequency;
|
|
if(randomd() < other.type.secretFrequency / total)
|
|
@node = other;
|
|
}
|
|
|
|
if(node is null)
|
|
break;
|
|
|
|
node.secretPicked = true;
|
|
}
|
|
}
|
|
|
|
void getTechnologyNodes() {
|
|
if(grid is null)
|
|
return;
|
|
ReadLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i)
|
|
yield(grid.nodes[i]);
|
|
}
|
|
|
|
void getResearchingNodes() {
|
|
if(grid is null)
|
|
return;
|
|
ReadLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
auto@ node = grid.nodes[i];
|
|
if(!node.bought)
|
|
continue;
|
|
if(node.unlocked)
|
|
continue;
|
|
if(!node.unlockable)
|
|
continue;
|
|
yield(grid.nodes[i]);
|
|
}
|
|
}
|
|
|
|
void getTechnologyNode(int id) {
|
|
ReadLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
if(id == grid.nodes[i].id) {
|
|
yield(grid.nodes[i]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void getTechnologyNode(vec2i pos) {
|
|
ReadLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
if(pos == grid.nodes[i].position) {
|
|
yield(grid.nodes[i]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
TechnologyNode@ getNode(int id) {
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
if(grid.nodes[i].id == id)
|
|
return grid.nodes[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void setResearchQueued(Empire& emp, int id, bool queued) {
|
|
WriteLock lock(mtx);
|
|
auto@ node = getNode(id);
|
|
if(node is null)
|
|
return;
|
|
if(node.bought)
|
|
return;
|
|
|
|
node.queued = queued;
|
|
}
|
|
|
|
void research(Empire& emp, int id, bool secondary = false, bool queue = false) {
|
|
WriteLock lock(mtx);
|
|
auto@ node = getNode(id);
|
|
if(node is null)
|
|
return;
|
|
if(node.bought)
|
|
return;
|
|
if(!node.canUnlock(emp)) {
|
|
if(queue && !secondary) {
|
|
node.queued = true;
|
|
delta = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto cost = node.getPointCost(emp);
|
|
if(secondary) {
|
|
if(emp.ForbidSecondaryUnlock != 0 && cost != 0)
|
|
return;
|
|
if(!node.canSecondaryUnlock(emp))
|
|
return;
|
|
if(!node.consumeSecondary(emp))
|
|
return;
|
|
node.secondaryUnlock = true;
|
|
totalGenerated += node.getPointCost(emp);
|
|
}
|
|
else {
|
|
if(cost == 0)
|
|
return;
|
|
if(cost > points) {
|
|
if(queue) {
|
|
node.queued = true;
|
|
delta = true;
|
|
}
|
|
return;
|
|
}
|
|
points -= cost;
|
|
}
|
|
|
|
node.buy(emp);
|
|
grid.markBought(node.position, emp);
|
|
delta = true;
|
|
}
|
|
|
|
bool isTagUnlocked(int id) {
|
|
if(id < 0 || uint(id) >= tagUnlocks.length)
|
|
return false;
|
|
return tagUnlocks[id] > 0;
|
|
}
|
|
|
|
void setTagUnlocked(int id, bool unlocked) {
|
|
if(id < 0 || uint(id) >= tagUnlocks.length) {
|
|
error("Error: cannot set unlocked for out-of-range tag "+id+" - "+getUnlockTagIdent(id));
|
|
return;
|
|
}
|
|
Lock lck(unlockMtx);
|
|
if(unlocked)
|
|
tagUnlocks[id] += 1;
|
|
else
|
|
tagUnlocks[id] -= 1;
|
|
unlockDelta = true;
|
|
}
|
|
|
|
void removeResearchOfType(int typeId) {
|
|
WriteLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
auto@ node = grid.nodes[i];
|
|
if(int(node.type.id) == typeId) {
|
|
if(!node.unlocked) {
|
|
grid.nodes.removeAt(i);
|
|
--i;
|
|
--cnt;
|
|
}
|
|
}
|
|
}
|
|
grid.regenGrid();
|
|
gridDelta = true;
|
|
}
|
|
|
|
void replaceResearchAt(vec2i pos, int replaceWith) {
|
|
auto@ otherType = getTechnology(replaceWith);
|
|
if(otherType is null)
|
|
return;
|
|
|
|
WriteLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
auto@ node = grid.nodes[i];
|
|
if(node.position == pos) {
|
|
if(!node.unlocked) {
|
|
@node.type = otherType;
|
|
if(otherType.defaultUnlock)
|
|
grid.markUnlocked(node.position);
|
|
}
|
|
}
|
|
}
|
|
gridDelta = true;
|
|
}
|
|
|
|
void replaceResearchOfType(int typeId, int replaceWith) {
|
|
auto@ otherType = getTechnology(replaceWith);
|
|
if(otherType is null)
|
|
return;
|
|
|
|
WriteLock lock(mtx);
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
auto@ node = grid.nodes[i];
|
|
if(int(node.type.id) == typeId) {
|
|
if(!node.unlocked) {
|
|
@node.type = otherType;
|
|
if(otherType.defaultUnlock)
|
|
grid.markUnlocked(node.position);
|
|
}
|
|
}
|
|
}
|
|
gridDelta = true;
|
|
}
|
|
|
|
void replaceResearchGrid(string name) {
|
|
auto@ gridType = getTechnologyGridSpec(name);
|
|
if(gridType is null)
|
|
return;
|
|
|
|
WriteLock lock(mtx);
|
|
@grid = gridType.create();
|
|
gridDelta = true;
|
|
}
|
|
|
|
void overlayResearchGrid(string name) {
|
|
auto@ gridType = getTechnologyGridSpec(name);
|
|
if(gridType is null)
|
|
return;
|
|
|
|
WriteLock lock(mtx);
|
|
@grid = gridType.create();
|
|
gridDelta = true;
|
|
}
|
|
|
|
void revealSecretProject(Empire& emp, bool pickedOnly) {
|
|
WriteLock lock(mtx);
|
|
|
|
TechnologyNode@ pick;
|
|
double count = 0;
|
|
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
auto@ node = grid.nodes[i];
|
|
if(!node.secret)
|
|
continue;
|
|
if(pickedOnly && !node.secretPicked)
|
|
continue;
|
|
if(node.available)
|
|
continue;
|
|
if(!node.canBeSecret(emp))
|
|
continue;
|
|
|
|
count += 1.0;
|
|
if(randomd() < 1.0 / count)
|
|
@pick = node;
|
|
}
|
|
|
|
if(pick !is null) {
|
|
pick.secretPicked = true;
|
|
pick.secret = false;
|
|
}
|
|
}
|
|
|
|
//Networking
|
|
void writeResearch(Message& msg, bool initial) {
|
|
ReadLock lock(mtx);
|
|
msg << researchRate;
|
|
msg << points;
|
|
msg << totalGenerated;
|
|
|
|
if(initial) {
|
|
msg.write1();
|
|
msg.write1();
|
|
}
|
|
else {
|
|
msg.writeBit(delta);
|
|
msg.writeBit(gridDelta);
|
|
}
|
|
|
|
if(initial || unlockDelta) {
|
|
msg.write1();
|
|
uint cnt = tagUnlocks.length;
|
|
msg.writeSmall(cnt);
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg.writeBit(tagUnlocks[i] > 0);
|
|
if(!initial)
|
|
unlockDelta = false;
|
|
}
|
|
else {
|
|
msg.write0();
|
|
}
|
|
|
|
if(initial || delta || gridDelta) {
|
|
if(initial || gridDelta) {
|
|
msg << grid.minPos;
|
|
msg << grid.maxPos;
|
|
msg << grid.nodes.length;
|
|
}
|
|
|
|
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
|
|
if(initial || gridDelta)
|
|
grid.nodes[i].write(msg);
|
|
else
|
|
grid.nodes[i].writeStatus(msg);
|
|
}
|
|
|
|
if(!initial) {
|
|
delta = false;
|
|
gridDelta = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Skip over data from old savegames
|
|
void loadOld(SaveFile& file) {
|
|
int tmp = 0;
|
|
int64 tmp64 = 0;
|
|
double tmpD = 0;
|
|
bool tmpB = false;
|
|
Object@ tmpO;
|
|
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
//load field
|
|
file >> tmp;
|
|
file >> tmp;
|
|
if(file < SV_0072)
|
|
file >> tmp;
|
|
file >> tmpD;
|
|
file >> tmpD;
|
|
}
|
|
|
|
if(file.readBit())
|
|
file >> tmp;
|
|
|
|
file >> researchRate;
|
|
file >> tmpD;
|
|
file >> tmp;
|
|
file >> tmp;
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
//load project
|
|
file >> tmp;
|
|
uint type = 0;
|
|
file >> type;
|
|
for(uint n = 0; n < 7; ++n)
|
|
file >> tmp;
|
|
file >> tmpB;
|
|
//load hooks
|
|
if(tmpB) {
|
|
if(type == 24 || type == 25) {
|
|
file >> tmpD;
|
|
}
|
|
else if(type == 27) {
|
|
uint sub = 0;
|
|
file >> sub;
|
|
for(uint j = 0; j < sub; ++j) {
|
|
file >> tmpO;
|
|
file >> tmpD;
|
|
file >> tmpB;
|
|
if(tmpB && file >= SV_0013)
|
|
file >> tmp64;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
file >> tmp;
|
|
for(uint i = 0; i < 7; ++i)
|
|
file >> tmp;
|
|
}
|
|
};
|