diff --git a/js/diplomacy.js b/js/diplomacy.js
index d64a463ad..0672ffa78 100644
--- a/js/diplomacy.js
+++ b/js/diplomacy.js
@@ -237,11 +237,15 @@ dojo.declare("classes.managers.DiplomacyManager", null, {
"name", "embassyLevel", "unlocked", "collapsed", "energy", "duration", "pinned"
])
};
+ if (this.get("leviathans").autoPinned) {
+ saveData.diplomacy.autoPinLeviathans = true;
+ }
},
load: function(saveData){
if (saveData.diplomacy) {
this.game.bld.loadMetadata(this.races, saveData.diplomacy.races);
+ this.get("leviathans").autoPinned = saveData.diplomacy.autoPinLeviathans || false;
}
},
diff --git a/js/jsx/left.jsx.js b/js/jsx/left.jsx.js
index 94c4f0902..0c72eeb1f 100644
--- a/js/jsx/left.jsx.js
+++ b/js/jsx/left.jsx.js
@@ -47,54 +47,33 @@ WCollapsiblePanel = React.createClass({
WResourceRow = React.createClass({
getDefaultProperties: function(){
- return {resource: null, isEditMode: false, isRequired: false, showHiddenResources: false};
+ return {resource: null, isEditMode: false, isRequired: false, showHiddenResources: false, isTemporalParadox: false};
},
- //this is a bit ugly, probably React.PureComponent + immutable would be a much better approach
- //TODO: this function needs a proper rewrite. I'm pretty sure it doesn't work at all as intended.
+ //I'm going for a solution that is technically less accurate, but is much simpler to understand.
+ //This function tells React to skip rendering for invisible resources.
+ //I think it's good enough for now.
shouldComponentUpdate: function(nextProp, nextState){
- var oldRes = this.oldRes || {},
- newRes = nextProp.resource;
-
- var isEqual =
- oldRes.value == newRes.value &&
- oldRes.maxValue == newRes.maxValue &&
- oldRes.perTickCached == newRes.perTickCached &&
- this.props.isEditMode == nextProp.isEditMode &&
- this.props.isRequired == nextProp.isRequired &&
- this.props.showHiddenResources == nextProp.showHiddenResources &&
- this.props.isTemporalParadox != nextProp.isTemporalParadox &&
- true /*this.state.visible == nextState.visible*/;
-
- if (isEqual){
- return false;
+ var newVisibility = this.getIsResInMainTable(nextProp.resource, nextProp);
+ if (this.oldVisibility !== newVisibility) {
+ //Remember new visibility state
+ this.oldVisibility = newVisibility;
+ //Resource will appear/disappear, therefore component should update
+ return true;
}
- this.oldRes = {
- value: newRes.value,
- maxValue: newRes.maxValue,
- perTickCached: newRes.perTickCached
- };
- return true;
+ //Update component if it will be displayed
+ //Don't update component if it won't be displayed
+ return newVisibility;
},
render: function(){
var res = this.props.resource;
- if (!res.visible && !this.props.showHiddenResources){
+ //Only render this resource if it's unlocked, visible, etc.
+ if (!this.getIsResInMainTable()) {
return null;
}
- var hasVisibility = (res.unlocked || (res.name == "kittens" && res.maxValue));
-
- if (!hasVisibility || (!this.getIsVisible() && !this.props.isEditMode)){
- return null;
- }
-
- //migrate dual resources (e.g. blueprint) to lower table once craft recipe is unlocked
- if (game.resPool.isNormalCraftableResource(res) && game.workshop.getCraft(res.name).unlocked){
- return null;
- }
-
//wtf is this code
var isTimeParadox = this.props.isTemporalParadox;
@@ -207,7 +186,13 @@ WResourceRow = React.createClass({
(!res.visible ? " hidden" : "")
;
- var resPercent = ((res.value / res.maxValue) * 100).toFixed();
+ var resPercent = "";
+ if (res.maxValue) {
+ resPercent = ((res.value / res.maxValue) * 100).toFixed() + "%/" + game.getDisplayValueExt(res.maxValue);
+ } else {
+ //Display value with 1 digit of precision
+ resPercent = game.getDisplayValueExt(Math.round(res.value), false /*prefix*/, false /*perTickHack*/, 1);
+ }
return $r("div", {role: "row", className: resRowClass}, [
this.props.isEditMode ?
@@ -225,7 +210,7 @@ WResourceRow = React.createClass({
className:"res-cell resource-name",
style:resNameCss,
onClick: this.onClickName,
- title: (res.title || res.name) + " " + resPercent + "%/" + game.getDisplayValueExt(res.maxValue) + " " + perTickVal,
+ title: (res.title || res.name) + " " + resPercent + " " + perTickVal,
role: "gridcell",
userFocus:"normal",
tabIndex: 0,
@@ -248,11 +233,35 @@ WResourceRow = React.createClass({
},
toggleView: function(){
- this.props.resource.isHidden = !this.props.resource.isHidden;
+ game.resPool.setResourceIsHidden(this.props.resource.name, !this.props.resource.isHidden);
},
- getIsVisible: function() {
- return !this.props.resource.isHidden;
+ //Parameter is optional.
+ //If not given, defaults to this.props.resource
+ getIsVisible: function(res) {
+ res = res || this.props.resource;
+ return !res.isHidden;
+ },
+
+ //Both parameters are optional.
+ //If not given, they default to this.props
+ getIsResInMainTable: function(res, props) {
+ res = res || this.props.resource;
+ props = props || this.props;
+
+ if (!res.visible && !props.showHiddenResources){
+ return false;
+ }
+ var hasVisibility = (res.unlocked || (res.name == "kittens" && res.maxValue));
+ if (!hasVisibility || (!this.getIsVisible(res) && !props.isEditMode)){
+ return false;
+ }
+ //migrate dual resources (e.g. blueprint) to lower table once craft recipe is unlocked
+ if (game.resPool.isNormalCraftableResource(res) && game.workshop.getCraft(res.name).unlocked){
+ return false;
+ }
+ //Else, resource is visible && unlocked && not displayed somewhere else instead:
+ return true;
},
componentDidMount: function(){
@@ -405,33 +414,31 @@ WCraftShortcut = React.createClass({
WCraftRow = React.createClass({
getDefaultProperties: function(){
- return {resource: null, isEditMode: false};
+ return {resource: null, isEditMode: false, isRequired: false};
},
+ //I'm going for a solution that is technically less accurate, but is much simpler to understand.
+ //This function tells React to skip rendering for invisible resources.
+ //I think it's good enough for now.
shouldComponentUpdate: function(nextProp, nextState){
- var newRes = nextProp.resource;
-
- /*var isEqual =
- oldRes.value == newRes.value &&
- this.props.isEditMode == nextProp.isEditMode &&
- this.props.isRequired == nextProp.isRequired &&
- this.state.visible == nextState.visible;
-
- if (isEqual){
- return false;
- }*/
- this.oldRes = {
- value: newRes.value,
- };
- return true;
+ var newVisibility = this.getIsResInCraftTable(nextProp.resource, nextProp);
+ if (this.oldVisibility !== newVisibility) {
+ //Remember new visibility state
+ this.oldVisibility = newVisibility;
+ //Resource will appear/disappear, therefore component should update
+ return true;
+ }
+ //Update component if it will be displayed
+ //Don't update component if it won't be displayed
+ return newVisibility;
},
render: function(){
var res = this.props.resource;
-
var recipe = game.workshop.getCraft(res.name);
- var hasVisibility = (res.unlocked && recipe.unlocked /*&& this.workshop.on > 0*/);
- if (!hasVisibility || (!this.getIsVisible() && !this.props.isEditMode)){
+
+ //Only render if this resource is unlocked, not marked as hidden, etc.
+ if (!this.getIsResInCraftTable()) {
return null;
}
@@ -502,8 +509,10 @@ WCraftRow = React.createClass({
}
},
- getIsVisible: function() {
- var res = this.props.resource;
+ //Parameter is optional.
+ //If not given, defaults to this.props.resource
+ getIsVisible: function(res) {
+ res = res || this.props.resource;
if (res.name == "wood") {
//(Wood is special because it appears twice, separately)
return !res.isHiddenFromCrafting;
@@ -511,6 +520,20 @@ WCraftRow = React.createClass({
return !res.isHidden;
},
+ //Both parameters are optional.
+ //If not given, they default to this.props
+ getIsResInCraftTable: function(res, props) {
+ res = res || this.props.resource;
+ props = props || this.props;
+
+ var recipe = game.workshop.getCraft(res.name);
+ var hasVisibility = (res.unlocked && recipe.unlocked);
+ if (!hasVisibility || (!this.getIsVisible(res) && !props.isEditMode)){
+ return false;
+ }
+ return true;
+ },
+
componentDidMount: function(){
var node = React.findDOMNode(this.refs.perTickNode);
if (node){
@@ -542,8 +565,7 @@ WResourceTable = React.createClass({
getInitialState: function(){
return {
isEditMode: false,
- isCollapsed: false,
- showHiddenResources: false
+ isCollapsed: false
};
},
render: function(){
@@ -557,7 +579,7 @@ WResourceTable = React.createClass({
resource: res,
isEditMode: this.state.isEditMode,
isRequired: isRequired,
- showHiddenResources: this.state.showHiddenResources,
+ showHiddenResources: game.resPool.showHiddenResources,
isTemporalParadox: game.calendar.day < 0
})
);
@@ -607,7 +629,7 @@ WResourceTable = React.createClass({
$r("div", {className:"res-toggle-hidden"}, [
$r("input", {
type:"checkbox",
- checked: this.state.showHiddenResources,
+ checked: game.resPool.showHiddenResources,
onClick: this.toggleHiddenResources,
style:{display:"inline-block"},
}),
@@ -631,7 +653,7 @@ WResourceTable = React.createClass({
},
toggleHiddenResources: function(e){
- this.setState({showHiddenResources: e.target.checked});
+ game.resPool.showHiddenResources = e.target.checked;
}
});
diff --git a/js/resources.js b/js/resources.js
index d91453359..66e354ffd 100644
--- a/js/resources.js
+++ b/js/resources.js
@@ -528,6 +528,8 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
energyCons: 0,
isLocked: false,
+ showHiddenResources: false, //Whether to show stuff like flux, gflops, & pseudo-resources
+ hiddenPseudoResources: null, //Array of names of pseudo-resources to mark as hidden
constructor: function(game){
this.game = game;
@@ -535,6 +537,8 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
this.resources = [];
this.resourceMap = {};
+ this.hiddenPseudoResources = [];
+
for (var i = 0; i < this.resourceData.length; i++){
var res = dojo.clone(this.resourceData[i]);
res.value = 0;
@@ -564,30 +568,42 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
//put your custom fake resources there
getPseudoResources: function(){
- return [
+ var pactsManager = this.game.religion.pactsManager;
+ var pseudoResArr = [
{
name: "worship",
title: $I("resources.worship.title"),
value: this.game.religion.faith,
- unlocked: true,
- visible: false
+ unlocked: this.game.religion.faith > 0,
+ visible: false //Doesn't normally appear in resource table
},
{
name: "epiphany",
title: $I("resources.epiphany.title"),
value: this.game.religion.faithRatio,
- unlocked: true,
+ unlocked: this.game.religion.faithRatio > 0 &&
+ this.game.science.get("theology").researched &&
+ !this.game.challenges.isActive("atheism"),
visible: false
},
{
name: "necrocornDeficit",
title: $I("resources.necrocornDeficit.title"),
- value: this.game.religion.pactsManager.necrocornDeficit,
- unlocked: true,
+ value: pactsManager.necrocornDeficit,
+ maxValue: pactsManager.fractureNecrocornDeficit,
+ unlocked: pactsManager.necrocornDeficit > 0 ||
+ //Do we have at least 1 pact purchased?
+ pactsManager.pacts.some(function(pact) {
+ return pact.val > 0;
+ }),
visible: false,
color: "#E00000"}
];
- //TODO: mixin unlocked and visible automatically
+ //Apply settings to mark as hidden:
+ for (var i = 0; i < pseudoResArr.length; i += 1) {
+ pseudoResArr[i].isHidden = this.hiddenPseudoResources.includes(pseudoResArr[i].name);
+ }
+ return pseudoResArr;
},
createResource: function(name){
@@ -926,6 +942,8 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
resetState: function(){
this.isLocked = false;
+ this.showHiddenResources = false;
+ this.hiddenPseudoResources = [];
for (var i = 0; i < this.resources.length; i++){
var res = this.resources[i];
res.value = 0;
@@ -942,8 +960,22 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
save: function(saveData){
saveData.res = {
- isLocked: this.isLocked
+ isLocked: this.isLocked,
+ showHiddenResources: this.showHiddenResources || undefined
};
+ //Save flags on which pseudo-resources to hide
+ //But for sanity purposes, only save a pseudo-resource if it actually exists!
+ var pseudoResources = this.getPseudoResources();
+ for (var i = 0; i < pseudoResources.length; i += 1) {
+ var resName = pseudoResources[i].name;
+ if (this.hiddenPseudoResources.includes(resName)) {
+ //We've found a legit pseudo-resource to be marked as hidden
+ if (!saveData.res.hiddenPseudoResources) {
+ saveData.res.hiddenPseudoResources = [];
+ }
+ saveData.res.hiddenPseudoResources.push(resName);
+ }
+ }
},
load: function(saveData){
@@ -951,6 +983,8 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
if (saveData.res){
this.isLocked = Boolean(saveData.res.isLocked);
+ this.showHiddenResources = Boolean(saveData.res.showHiddenResources);
+ this.hiddenPseudoResources = saveData.res.hiddenPseudoResources || [];
}
},
@@ -1054,6 +1088,41 @@ dojo.declare("classes.managers.ResourceManager", com.nuclearunicorn.core.TabMana
}
},
+ //Sets the "isHidden" flag of a resource or a pseudo-resource.
+ //This exists because the UI code can't know if a resource is real or pseudo.
+ //Wood is a special case because it has 2 flags. We don't handle that special case here.
+ setResourceIsHidden: function(resName, hide) {
+ if (typeof(resName) != "string") {
+ console.error("Can't set res.isHidden; resource name must be a string.");
+ return;
+ }
+ if (typeof(hide) != "boolean") {
+ console.error("Can't set res.isHidden; flag must be a boolean.");
+ return;
+ }
+
+ var res = this.get(resName);
+ if (res) {
+ //We have a real resource!
+ res.isHidden = hide;
+ return;
+ }
+ //Else, assume we're dealing with a pseudo-resource.
+ if (this.hiddenPseudoResources.includes(resName)) {
+ if (!hide) {
+ //Remove by value
+ this.hiddenPseudoResources = this.hiddenPseudoResources.filter(function(elem) {
+ return elem != resName;
+ });
+ }
+ } else {
+ //Pseudo-resource is not already in array.
+ if (hide) {
+ this.hiddenPseudoResources.push(resName);
+ }
+ }
+ },
+
toggleLock: function(){
this.isLocked = !this.isLocked;
},
diff --git a/js/workshop.js b/js/workshop.js
index 35209de16..eb7d2f820 100644
--- a/js/workshop.js
+++ b/js/workshop.js
@@ -2865,8 +2865,15 @@ dojo.declare("com.nuclearunicorn.game.ui.CraftButtonController", com.nuclearunic
}
if (craft.value != 0) {
- var countdown = (1 / (this.game.workshop.getEffectEngineer(craft.name, false) * this.game.getTicksPerSecondUI())).toFixed(0);
- desc += "
=> " + $I("workshop.craftBtn.desc.countdown", [countdown]);
+ var craftsPerSecond = (this.game.workshop.getEffectEngineer(craft.name, false) * this.game.getTicksPerSecondUI());
+ if (craftsPerSecond <= 0) {
+ desc += "
=> " + $I("workshop.craftBtn.desc.countdown", ["∞"]);
+ } else if (craftsPerSecond <= 0.5) {
+ var countdown = (1 / craftsPerSecond).toFixed(0);
+ desc += "
=> " + $I("workshop.craftBtn.desc.countdown", [countdown]);
+ } else {
+ desc += "
=> " + $I("workshop.craftBtn.desc.craftsPerSecond", [this.game.getDisplayValueExt(craftsPerSecond)]);
+ }
}
}
return desc;
diff --git a/res/i18n/en.json b/res/i18n/en.json
index 9ebc65d05..ab4a78584 100644
--- a/res/i18n/en.json
+++ b/res/i18n/en.json
@@ -336,7 +336,7 @@
"challendge.pacifism.effect.desc": "You gain improved trade based on trade posts.",
"challendge.postApocalypse.label": "Post Apocalypse",
"challendge.postApocalypse.desc": "You found a broken timeline with extreme pollution. You can send your strongest kittens to clear this mess. Pollution has more negative effects.
WARNING: you have limited time to establish food production after starting this challenge.",
- "challendge.postApocalypse.effect.desc": "Increases amount of cryochaimbers that can be operational. Unlocks special policies for this run.",
+ "challendge.postApocalypse.effect.desc": "Increases amount of cryochambers that can be operational. Unlocks special policies for this run.",
"challendge.postApocalypse.flavor": "'Surely it's not that bad' they thought",
"challendge.reservesReclaimed.msg": "Reserves were reclaimed!",
"challendge.unicornTears.label": "Unicorn Tears",
@@ -2192,8 +2192,9 @@
"workshop.concreteWarehouses.label": "Concrete Warehouses",
"workshop.concreteWarehouses.desc": "Storage facilities store 35% more resources",
"workshop.craft.effectiveness": "Craft effectiveness: +{0}%",
- "workshop.craftBtn.desc.countdown": "One craft every: {0}sec",
+ "workshop.craftBtn.desc.countdown": "One craft every: {0} sec",
"workshop.craftBtn.desc.craftRatio": "Engineers expertise",
+ "workshop.craftBtn.desc.craftsPerSecond": "{0} crafts per second",
"workshop.craftBtn.desc.effectivenessBonus": "Craft effectiveness bonus: +{0}%",
"workshop.craftBtn.desc.progressHandicap": "Craft difficulty",
"workshop.craftBtn.desc.tier": "Engineer's optimal rank",
diff --git a/res/theme_arctic.css b/res/theme_arctic.css
index 35a1b74b5..08c95c963 100644
--- a/res/theme_arctic.css
+++ b/res/theme_arctic.css
@@ -18,7 +18,7 @@
/* ************* */
/* *** FONTS *** */
/* ************* */
-@import url(https://fonts.googleapis.com/css?family=Source+Sans+KR:wght@300,400,700&display=swap);
+@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:wght@300,400,700&display=swap);
/* ******************************************** */
/* ******************* GAME ******************* */
diff --git a/res/theme_factory.css b/res/theme_factory.css
index 9f9d14435..fe2f7a450 100644
--- a/res/theme_factory.css
+++ b/res/theme_factory.css
@@ -5,7 +5,7 @@
/* ************* */
/* *** FONTS *** */
/* ************* */
-@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght,CASL@,300..700,0..1&display=swap");
+@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:ital,wght@0,100..900;1,100..900&display=swap");
/* ******************************************** */
/* ******************* GAME ******************* */