22 Star 145 Fork 71

锅巴汉化 / 猫国建设者(Kittens Game)

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
core.js 61.02 KB
一键复制 编辑 原始数据 按行查看 历史
petercheney 提交于 2023-11-21 20:22 . 翻译
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459
/**
* Workaround for IE9 local storage :V
*
* This fix is intended for IE in general and especially for IE9,
* where localStorage is defined as system variable.
*
*/
window.LCstorage = window.localStorage;
if (document.all && !window.localStorage) {
window.LCstorage = {};
window.LCstorage.removeItem = function () { };
}
dojo.declare("com.nuclearunicorn.core.Control", null, {
//Base control class. Must be a superclass for all game components.
});
/**
* core.js - a collection of base classes shared among all components of the game.
* UI controls go there.
*
* This should be your starting point to get familiar with the KG codebase. It has the most comments and explanation on some of KG's idiosincracies.
* The next file to check is `game.js`
*/
/**
* A base class for every tab manager component like science, village, bld, etc
* Ideally every manager should be a subclass of a TabManager. See reference implementation in religion.js
*/
dojo.declare("com.nuclearunicorn.core.TabManager", com.nuclearunicorn.core.Control, {
/**
* This may not be obvious, but all objects instantiated there will be STATIC and shared among all the instances of the class.
*
* Wrong:
*
* >> arrayField: []
*
* Correct:
*
* >> arrayField: null,
* >>
* >> constructor: function() { this.arrayField = []; }
*/
effectsCachedExisting: null,
meta: null,
panelData: null,
/**
* Constructors are INHERITED automatically and CHAINED in the class hierarchy
*/
constructor: function(){
this.effectsCachedExisting = {};
this.meta = [];
this.panelData = {};
},
/**
* Methods however are NOT. Use this.inherited(arguments) to call a base method;
*/
registerPanel: function(id, panel){
if (!this.panelData[id]){
this.panelData[id] = {
collapsed: panel.collapsed
};
}
panel.collapsed = this.panelData[id].collapsed;
dojo.connect(panel, "onToggle", this, function(collapsed){
this.panelData[id].collapsed = collapsed;
});
},
/**
* @param meta - metadata set (e.g. buildings list, upgrades list, etc)
* @param provider - any object having getEffect(metaElem, effectName) method
*/
registerMeta: function(type, meta, provider){
if (!type) {
this.meta.push({meta: meta, provider: provider});
} else if (type == "research") {
this.meta.push({
meta: meta,
provider: { getEffect : function(item, effect){
return (item.researched && item.effects) ? item.effects[effect] : 0;
}}
});
} else if (type == "stackable") {
this.meta.push({
meta: meta,
provider: { getEffect : function(item, effect){
return (item.effects) ? item.effects[effect] * item.on : 0;
}}
});
}
},
/*
TODO: do we need this? can this be simplified?
*/
setEffectsCachedExisting: function() {
// Set effectsCachedExisting based on meta
for (var a = 0; a < this.meta.length; a++){
if (this.meta[a].meta){
for (var i = 0; i < this.meta[a].meta.length; i++){
for (var effect in this.meta[a].meta[i].effects) {
this.effectsCachedExisting[effect] = 0;
}
}
}
}
// Set effectsCachedExisting based on effectsBase
if (typeof(this.effectsBase) == "object") {
for (var effect in this.effectsBase) {
this.effectsCachedExisting[effect] = 0;
}
}
},
updateEffectCached: function() {
var effectsBase = this.effectsBase;
if (effectsBase){
effectsBase = this.game.resPool.addBarnWarehouseRatio(effectsBase);
}
for (var i = 0; i < this.meta.length; i++){
this.updateMetaEffectCached(this.meta[i]);
}
for (var name in this.effectsCachedExisting) {
// Add effect from meta
var effect = 0;
for (var i = 0; i < this.meta.length; i++){
effect += this.getMetaEffect(name, this.meta[i]);
}
// Previously, catnip demand (or other buildings that both affect the same resource)
// could have theoretically had more than 100% reduction because they diminished separately,
// this takes the total effect and diminishes it as a whole.
if (this._hasLimitedDiminishingReturn(name) && effect !== 0) {
effect = this.game.getLimitedDR(effect, 1);
}
// Add effect from effectsBase
if (effectsBase && effectsBase[name]) {
effect += effectsBase[name];
}
// Add effect in globalEffectsCached, in addition of other managers
this.game.globalEffectsCached[name] = typeof(this.game.globalEffectsCached[name]) == "number" ? this.game.globalEffectsCached[name] + effect : effect;
}
},
updateMetaEffectCached: function (metadata) {
for (var i = 0; i < metadata.meta.length; i++){
var meta = metadata.meta[i];
meta.totalEffectsCached = {};
for (var effectName in this.effectsCachedExisting){
var effect;
if (metadata.provider){
effect = metadata.provider.getEffect(meta, effectName) || 0;
} else {
effect = meta.effects[effectName] || 0;
}
meta.totalEffectsCached[effectName] = effect;
}
}
},
_hasLimitedDiminishingReturn: function(name) {
return name == "catnipDemandRatio"
|| name == "fursDemandRatio"
|| name == "ivoryDemandRatio"
|| name == "spiceDemandRatio"
|| name == "unhappinessRatio";
},
/**
* Returns a cached combined value of effect of all managers, for effect existing in the manager
* Will calculate effect value of the manager if the value of effect of all managers is not yet implemented (launch of the game)
*/
/*
getEffect: function(name){
// Search only if effect exists in the manager
if (typeof(this.effectsCachedExisting[name]) == "undefined"){
return 0;
}
// Search only if effect is not yet in the globalEffectsCached
var cached = this.game.globalEffectsCached[name];
if (cached != undefined) {
return cached;
}
// Search
var effect = 0;
for (var i = 0; i< this.meta.length; i++){
var effectMeta = this.getMetaEffect(name, this.meta[i]);
effect += effectMeta;
}
return effect;
},
*/
/**
* Returns an effect from a generic array of effects like gamePage.bld.buildingsData
* Replacement for getEffect() method
*/
getMetaEffect: function(name, metadata){
var totalEffect = 0;
if (!metadata.meta){
return 0;
}
for (var i = 0; i < metadata.meta.length; i++){
var meta = metadata.meta[i];
//
// This is an ugly hack for managers like workshop or science
// Ideally just a getter handler should be called there returning correct value
//
var effect = 0;
if (meta.totalEffectsCached){
effect = meta.totalEffectsCached[name] || 0;
}
totalEffect += effect;
}
return totalEffect || 0;
},
getMeta: function(name, metadata){
for (var i = 0; i < metadata.length; i++){
var meta = metadata[i];
if (meta.name == name){
return meta;
}
}
console.error("Could not find metadata for ", name, "in", metadata);
},
loadMetadata: function(meta, saveMeta, metaId){
if (!saveMeta){
console.trace();
console.warn("Unable to load metadata table '" + metaId + "', save record is empty");
return;
}
for(var i = 0; i < saveMeta.length; i++){
var savedMetaElem = saveMeta[i];
if (savedMetaElem != null){
var elem = this.getMeta(savedMetaElem.name, meta);
if (!elem) { continue; }
for (var fld in savedMetaElem){
if (fld == name) {
continue;
}
if (!elem.hasOwnProperty(fld)){
console.warn("Can't find elem." + fld + " in", elem);
}
if (savedMetaElem[fld] !== undefined) {
if (savedMetaElem[fld] != null && typeof(savedMetaElem[fld]) == "object") {
this.loadMetadata(elem[fld], savedMetaElem[fld]);
} else {
elem[fld] = savedMetaElem[fld];
}
}
}
}
}
},
filterMetadata: function(meta, fields){
var filtered = [];
for(var i = 0; i < meta.length; i++){
var clone = {};
for (var j = 0; j < fields.length; j++){
var fld = fields[j];
/*if (!meta[i].hasOwnProperty(fld)){
console.warn("Can't find elem." + fld + " in", meta[i]);
}*/
clone[fld] = meta[i][fld];
}
filtered.push(clone);
}
return filtered;
},
//TODO: add saveMetadata
/**
* TODO: this logic is very confusing. Ideally the only place devs need to change should be building metadata.
*/
resetStateStackable: function(bld) {
bld.val = 0;
bld.on = 0;
if (bld.noStackable == "undefined") {
bld.noStackable = false;
}
if (bld.isAutomationEnabled != undefined) {
bld.isAutomationEnabled = null;
}
// Automatic settings of togglable
if (bld.lackResConvert != undefined) {
// Exceptions (when convertion is caused by an upgrade)
bld.togglable = true;
}
for (var effect in bld.effects) {
if (effect == "energyConsumption" || effect == "magnetoRatio" || effect == "productionRatio") {
// Exceptions (when energyConsumption is caused by an upgrade)
bld.togglable = (bld.name == "oilWell" || bld.name == "biolab" || bld.name == "chronosphere" || bld.name == "aiCore") ? false : true;
}
}
},
resetStateResearch: function() {
//TODO
}
});
/**
* Simple class from a right-sided console in the game UI
*
* TODO: all ui logic should be completely detached. Ideally game.msg should just post ("/msg") topic.
*/
dojo.declare("com.nuclearunicorn.game.log.Console", null, {
static: {
filters: {
"astronomicalEvent": {
title: $I("console.filter.astronomicalEvent"),
enabled: true,
unlocked: false
},
"hunt": {
title: $I("console.filter.hunt"),
enabled: true,
unlocked: false
},
"trade": {
title: $I("console.filter.trade"),
enabled: true,
unlocked: false
},
"craft": {
title: $I("console.filter.craft"),
enabled: true,
unlocked: false
},
"workshopAutomation": {
title: $I("console.filter.workshopAutomation"),
enabled: true,
unlocked: false
},
"meteor": {
title: $I("console.filter.meteor"),
enabled: true,
unlocked: false
},
"ivoryMeteor": {
title: $I("console.filter.ivoryMeteor"),
enabled: true,
unlocked: false
},
"unicornRift": {
title: $I("console.filter.unicornRift"),
enabled: true,
unlocked: false
},
"unicornSacrifice": {
title: $I("console.filter.unicornSacrifice"),
enabled: true,
unlocked: false
},
"alicornRift": {
title: $I("console.filter.alicornRift"),
enabled: true,
unlocked: false
},
"alicornSacrifice": {
title: $I("console.filter.alicornSacrifice"),
enabled: true,
unlocked: false
},
"alicornCorruption":{
title: $I("console.filter.alicornCorruption"),
enabled: true,
unlocked: false
},
"tcShatter": {
title: $I("console.filter.tcShatter"),
enabled: true,
unlocked: false
},
"tcRefine": {
title: $I("console.filter.tcRefine"),
enabled: true,
unlocked: false
},
"faith": {
title: $I("console.filter.faith"),
enabled: true,
unlocked: false
},
"elders": {
title: $I("console.filter.elders"),
enabled: true,
unlocked: false
},
"blackcoin": {
title: $I("console.filter.blackcoin"),
enabled: true,
unlocked: false
}
}
},
messages: null,
maxMessages: 40,
messageIdCounter: 0,
ui: null,
game: null,
constructor: function(game) {
this.game = game;
this.messages = [];
this.filters = dojo.clone(this.static.filters);
},
/**
* Prints message in the console. Returns a DOM node for the last created message
*/
msg : function(message, type, tag, noBullet) {
if (tag && this.filters[tag]){
var filter = this.filters[tag];
if (!filter.unlocked){
filter.unlocked = true;
this.ui.renderFilters();
} else if (!filter.enabled){
return;
}
}
var hasCalendarTech = this.game.science.get("calendar").researched;
var logmsg = {
text: message,
type: type,
tag: tag,
noBullet: noBullet,
id: "consoleMessage_" + (this.messageIdCounter++),
hasCalendarTech: hasCalendarTech,
year: hasCalendarTech ? this.game.calendar.year.toLocaleString() : null,
seasonTitle: hasCalendarTech ? this.game.calendar.getCurSeasonTitle() : null,
seasonTitleShorten: hasCalendarTech ? this.game.calendar.getCurSeasonTitleShorten() : null
};
this.messages.push(logmsg);
if (this.messages.length > this.maxMessages){
this.messages.shift();
}
this.ui.renderConsoleLog();
this.ui.notifyLogEvent(logmsg);
return logmsg;
},
clear: function(){
this.messages = [];
this.ui.renderConsoleLog();
},
resetState: function (){
for (var fId in this.filters){
var filter = this.filters[fId];
filter.unlocked = filter.defaultUnlocked || false;
filter.enabled = true;
}
//TODO: find usage and call ui.renderFilters
this.ui.renderFilters();
},
save: function(saveData){
var saveFilters = {};
for (var fId in this.filters) {
var filter = this.filters[fId];
saveFilters[fId] = {unlocked: filter.unlocked, enabled: filter.enabled};
}
saveData.console = {
filters: saveFilters
};
},
load: function(saveData){
if (saveData.console && saveData.console.filters){
for (var fId in saveData.console.filters){
var savedFilter = saveData.console.filters[fId];
if (this.filters[fId]) {
this.filters[fId].unlocked = savedFilter.unlocked;
this.filters[fId].enabled = savedFilter.enabled;
}
}
}
}
});
dojo.declare("com.nuclearunicorn.game.ui.ButtonController", null, {
game: null,
controllerOpts: null,
constructor: function(game, controllerOpts){
this.game = game;
if (!this.game) {
throw new Error("The game instance must be defined for the controller");
}
this.controllerOpts = controllerOpts || {};
},
fetchModel: function(options) {
var model = this.initModel(options);
model.name = this.getName(model);
model.description = this.getDescription(model);
model.prices = this.getPrices(model);
model.priceRatio = options.priceRatio;
model.handler = options.handler;
model.twoRow = options.twoRow;
this.updateEnabled(model);
this.updateVisible(model);
return model;
},
fetchExtendedModel: function(model) {
var prices = model.prices;
var priceModels = [];
if (prices) {
for( var i = 0; i < prices.length; i++){
var price = prices[i];
priceModels.push(this.createPriceLineModel(model, price));
}
}
model.priceModels = priceModels;
},
initModel: function(options) {
var mdl = this.defaults();
mdl.options = options;
return mdl;
},
defaults: function() {
return {
name: "",
description: "",
visible: true,
enabled: true,
handler: null,
prices: null,
priceRatio: null,
twoRow: null,
refundPercentage: 0.5,
// ---
highlightUnavailable: false,
resourceIsLimited: "",
multiplyEffects: false
};
},
createPriceLineModel: function(model, price) {
var res = this.game.resPool.get(price.name);
return {
title : res.title || res.name,
name: price.name,
val: price.val,
progress: res.value / price.val,
displayValue: this.game.getDisplayValueExt(price.val)
};
},
hasResources: function(model, prices){
if (!prices){
prices = this.getPrices(model);
}
return this.game.resPool.hasRes(prices);
},
updateEnabled: function(model){
model.enabled = this.hasResources(model, model.prices);
model.highlightUnavailable = this.game.opts.highlightUnavailable;
if (!this.game.opts.highlightUnavailable){
return;
}
//---------------------------------------------------
// a bit hackish place for price highlight
//---------------------------------------------------
var limited = this.game.resPool.isStorageLimited(model.prices);
//---- now highlight some stuff in vanilla js way ---
model.resourceIsLimited = limited;
},
updateVisible: function(model) {
//do nothing
if (this.controllerOpts && this.controllerOpts.updateVisible) {
this.controllerOpts.updateVisible.apply(this, arguments);
}
},
getPrices: function(model){
return model.options.prices || [];
},
getName: function(model){
if (this.controllerOpts.getName){
return this.controllerOpts.getName.apply(this, arguments);
}
return model.options.name;
},
getDescription: function(model){
return model.options.description;
},
/**
* Deprecated method for price management (increases price property stored in button)
*/
adjustPrice:function(model, ratio ){
var prices = this.getPrices(model);
if (prices.length){
for( var i = 0; i < prices.length; i++){
var price = prices[i];
price.val = price.val * ratio;
}
}
this.game.render();
},
/**
* Deprecated method for price management (same as above, but decreases price on sale)
*/
rejustPrice: function(model, ratio){
var prices = model.prices;
if (prices.length){
for( var i = 0; i < prices.length; i++){
var price = prices[i];
price.val = price.val / ratio;
}
}
this.game.render();
},
payPrice: function(model) {
this.game.resPool.payPrices(model.prices);
model.prices = this.getPrices(model);
},
payPriceForUndoRefund: function(model) {
//Shamelessly copied from the refund code, except we'll lose resources instead of gaining them!
if (!model.prices.length){
console.warn("unable pay prices for undo refund a building, no prices specified in metadata :O");
return;
}
for( var i = 0; i < model.prices.length; i++){
var price = model.prices[i];
var res = this.game.resPool.get(price.name);
if (res.isRefundable(this.game) && !price.isTemporary) {
this.game.resPool.addResEvent(price.name, -price.val * model.refundPercentage);
} else {
// No refund to undo
}
}
model.prices = this.getPrices(model);
},
clickHandler: function(model, event){
model.handler.apply(this, [model, event]);
},
buyItem: function(model, event, callback){
if (model.enabled && this.hasResources(model)) {
this.clickHandler(model, event);
this.payPrice(model);
if (model.priceRatio){
this.adjustPrice(model.priceRatio);
}
callback(true);
}
callback(false);
},
refund: function(model){
if (!model.prices.length){
console.warn("unable to refund building, no prices specified in metadata :O");
return;
}
for( var i = 0; i < model.prices.length; i++){
var price = model.prices[i];
var res = this.game.resPool.get(price.name);
if (res.isRefundable(this.game) && !price.isTemporary) {
this.game.resPool.addResEvent(price.name, price.val * model.refundPercentage);
} else {
// No refund at all
}
}
}
});
/**
* A base class for game button. Inventing the wheel since 2014
*/
dojo.declare("com.nuclearunicorn.game.ui.Button", com.nuclearunicorn.core.Control, {
model: null,
controller: null,
game: null,
//nodes
domNode: null,
container: null,
tab: null,
//--------------------
//left part of the button
buttonTitle: null,
constructor: function(opts, game){
this.game = game;
this.setOpts(opts);
this.init();
},
setOpts: function(opts){
this.id = opts.id;
this.controller = opts.controller;
if (!this.controller) {
throw new Error("Controller must be defined for the button");
}
//this.model = this.controller.fetchModel(opts);
//screw this
this.opts = opts;
},
//required by BuildingButton
init: function(){
},
updateVisible: function(){
if (!this.domNode){
return;
}
// locked structures are invisible
if (this.model.visible){
if (this.domNode.style.display === "none"){
this.domNode.style.display = "block";
}
} else {
if (this.domNode.style.display === "block"){
this.domNode.style.display = "none";
}
}
},
updateEnabled: function(){
if ( this.domNode ){
var hasClass = dojo.hasClass(this.domNode, "disabled");
var hasClassLimited = dojo.hasClass(this.domNode, "limited");
if (this.model.enabled){
if (hasClass){
dojo.removeClass(this.domNode, "disabled");
}
if (hasClassLimited){
dojo.removeClass(this.domNode, "limited");
}
} else {
if (!hasClass){
dojo.addClass(this.domNode, "disabled");
}
if (!hasClassLimited && this.model.resourceIsLimited){
dojo.addClass(this.domNode, "limited");
}
}
}
//---------------------------------------------------
// a bit hackish place for price highlight
//---------------------------------------------------
//---- now highlight some stuff in vanilla js way ---
},
update: function() {
this.model = this.controller.fetchModel(this.opts);
this.updateEnabled();
this.updateVisible();
if (this.buttonTitle && this.buttonTitle.innerHTML != this.model.name){
this.buttonTitle.innerHTML = this.model.name;
}
},
/**
* Renders button. Method is usually called once the tab is created.
*/
render: function(btnContainer){
this.model = this.controller.fetchModel(this.opts);
this.container = btnContainer;
this.domNode = dojo.create("div", {
style: {
position: "relative",
display: this.model.visible ? "block" : "none"
},
tabIndex: 0
}, btnContainer);
if (this.model.twoRow) {
dojo.style(this.domNode, "marginLeft", "auto");
dojo.style(this.domNode, "marginRight", "auto");
}
this.buttonContent = dojo.create("div", {
className: "btnContent",
title: this.model.description
}, this.domNode);
this.buttonTitle = dojo.create("span", {
innerHTML: this.model.name,
className: "btnTitle",
style: {}
}, this.buttonContent);
this.domNode.className = "btn nosel";
if (!this.model.enabled){
this.domNode.className += " disabled";
}
this.updateVisible();
dojo.connect(this.domNode, "onclick", this, "onClick");
dojo.connect(this.domNode, "onkeypress", this, "onKeyPress");
this.afterRender();
},
animate: function(){
var btnNode = jQuery(this.domNode);
btnNode.animate({
opacity: 0.5
}, 70, function(){
btnNode.animate({
opacity: 1.0
}, 70);
});
},
onClick: function(event){
this.animate();
var self = this;
this.controller.buyItem(this.model, event, function(result) {
if (result) {
self.update();
}
});
},
onKeyPress: function(event){
if (event.key == "Enter"){
this.onClick(event);
}
},
afterRender: function(){
var prices = this.model.prices;
if (prices.length && !this.tooltip){
var tooltip = dojo.create("div", {
classname: "button_tooltip",
style: {
display: "none",
border: "1px solid black",
marginLeft: "4px",
padding: "5px",
position: "absolute",
left: "170px",
top: "-1px",
width: "120px"
}}, this.domNode);
/**
* Create prices tooltip and store it inside of the button DOM node
*/
var tooltipPricesNodes = [];
for( var i = 0; i < prices.length; i++){
var price = prices[i];
var priceItemNode = dojo.create("div", {
style : {
overflow: "hidden"
}
}, tooltip);
var nameSpan = dojo.create("span", { innerHTML: price.title, style: { float: "left"} }, priceItemNode );
var priceSpan = dojo.create("span", { innerHTML: price.displayValue, style: {float: "right" } }, priceItemNode );
tooltipPricesNodes.push({ "name" : nameSpan, "price": priceSpan});
}
dojo.connect(this.domNode, "onmouseover", this, dojo.partial(function(tooltip){ dojo.style(tooltip, "display", ""); }, tooltip));
dojo.connect(this.domNode, "onmouseout", this, dojo.partial(function(tooltip){ dojo.style(tooltip, "display", "none"); }, tooltip));
this.tooltip = tooltip;
this.tooltipPricesNodes = tooltipPricesNodes;
}
},
//Fast access snippet to create button links like "on", "off", "sell", etc.
addLink: function(linkModel) {
var longTitleClass = (linkModel.title.length > 4) ? "small" : "";
var link = dojo.create("a", {
href: "#",
innerHTML: linkModel.title,
className: longTitleClass + (linkModel.cssClass ? (" " + linkModel.cssClass) : ""),
style:{
paddingLeft: "2px",
float: "right",
cursor: "pointer"
}
}, null);
var linkHandler = dojo.connect(link, "onclick", this, dojo.partial(function(handler, event){
event.stopPropagation();
event.preventDefault();
var self = this;
this.animate();
// FIXME should as easy as handler.apply(this, [args...])
dojo.hitch(this, handler, event, function(result) {
if (result) {
self.update();
}
})();
}, linkModel.handler));
dojo.place(link, this.buttonContent);
return {
link: link,
linkHandler: linkHandler
};
},
/*
* Add a link control with a collapsible menu of other links
*/
addLinkList: function(links){
var linkList = {};
var linksDiv = dojo.create("div", {
style: {
float: "right"
}
}, this.buttonContent);
var linksTooltip = dojo.create("div", {
className: "linkContent",
style: {
display: "none",
position: "absolute",
float: "right",
marginTop: "35px",
zIndex: "100"
},
}, linksDiv);
//linksTooltip.innerHTML = "<div>FOO</div><div>BAR</div><div>BAZ</div>";
if (!links.length){
return linkList;
}
//------------- root href --------------
var link = dojo.create("a", {
href: "#",
className: links[0].id ? (links[0].id + "Link") : "",
style: {
display: "block",
float: "right"
},
innerHTML: links[0].title,
title: links[0].alt || links[0].title
}, linksDiv);
linksTooltip.style.left = link.offsetLeft + 'px'; //hack hack hack
dojo.connect(link, "onclick", this, dojo.partial(function(handler, event){
event.stopPropagation();
event.preventDefault();
dojo.hitch(this, handler)();
this.update();
}, links[0].handler));
linkList[links[0].id] = { link : link };
if (links.length <= 1){
return linkList;
}
//-----------dropdown
dojo.connect(linksDiv, "onmouseover", this, dojo.partial(function(tooltip){ dojo.style(tooltip, "display", "block"); }, linksTooltip));
dojo.connect(linksDiv, "onmouseout", this, dojo.partial(function(tooltip){ dojo.style(tooltip, "display", "none"); }, linksTooltip));
for (var i = 1; i < links.length; i++){
var link = dojo.create("a", {
href: "#",
innerHTML: links[i].title,
title: links[i].alt || links[i].title,
className:"dropdown-link",
style:{
display: "block",
}
}, linksTooltip);
dojo.connect(link, "onclick", this, dojo.partial(function(handler, event){
event.stopPropagation();
event.preventDefault();
dojo.hitch(this, handler)();
this.update();
}, links[i].handler));
linkList[links[i].id] = { link : link };
}
return linkList;
}
});
dojo.declare("com.nuclearunicorn.game.ui.ButtonModernController", com.nuclearunicorn.game.ui.ButtonController, {
defaults: function() {
var result = this.inherited(arguments);
result.simplePrices = true;
result.hasResourceHover = false;
result.tooltipName = false;
return result;
},
getFlavor: function(model){
return undefined;
},
getEffects: function(model){
return undefined;
},
getTotalEffects: function(model){
return undefined;
},
getNextEffectValue: function(model, effectName) {
return undefined;
},
createPriceLineModel: function(model, price) {
return this._createPriceLineModel(price, model.simplePrices);
},
_createPriceLineModel: function(price, simplePrices, indent) {
var res = this.game.resPool.get(price.name);
var hasRes = res.value >= price.val;
var hasLimitIssue = res.maxValue && ((price.val > res.maxValue && !indent) || price.baseVal > res.maxValue);
var asterisk = hasLimitIssue ? "*" : ""; //mark limit issues with asterisk
var displayValue = hasRes || simplePrices
? this.game.getDisplayValueExt(price.val)
: this.game.getDisplayValueExt(res.value) + " / " + this.game.getDisplayValueExt(price.val) + asterisk;
var resPerTick = this.game.getResourcePerTick(res.name, true);
var eta = 0;
if (!hasRes && resPerTick > 0 && !simplePrices) {
eta = (price.val - res.value) / (resPerTick * this.game.getTicksPerSecondUI());
if (eta >= 1) {
displayValue += " (" + this.game.toDisplaySeconds(eta) + ")";
}
}
if (!indent) {
indent = 0;
}
var result = {
title : res.title || res.name,
name: price.name,
val: price.val,
hasResources: hasRes,
displayValue: displayValue,
indent: indent,
eta: eta,
hasLimitIssue: hasLimitIssue
};
//unroll prices to the raw resources
if (!hasRes && res.craftable && !simplePrices && res.name != "wood") {
var craft = this.game.workshop.getCraft(res.name);
if (craft.unlocked) {
var craftRatio = this.game.getResCraftRatio(res.name);
result.title = "+ " + result.title;
result.children = [];
var components = this.game.workshop.getCraftPrice(craft);
for (var j in components) {
var diff = price.val - res.value;
// Round up to the nearest craftable amount
var val = Math.ceil(components[j].val * diff / (1 + craftRatio));
var remainder = val % components[j].val;
if (remainder != 0) {
val += components[j].val - remainder;
}
var comp = {name: components[j].name, val: val, baseVal: components[j].val};
var compResult = this._createPriceLineModel(comp, simplePrices, indent + 1);
result.children.push(compResult);
}
}
}
return result;
},
fetchExtendedModel: function(model) {
this.inherited(arguments);
model.flavor = this.getFlavor(model);
this.updateEffectModels(model);
},
updateEffectModels: function(model) {
var effectsList = this.getEffects(model);
model.effectModels = [];
if (!effectsList || Object.keys(effectsList).length === 0) {
return;
}
//-----------------------------------------
var displayEffects = effectsList;
var isEffectMultiplierEnabled = model.multiplyEffects && this.game.ui.isEffectMultiplierEnabled();
if (isEffectMultiplierEnabled) {
displayEffects = this.getTotalEffects(model) || effectsList;
}
for (var effectName in effectsList) {
var effectMeta = this.game.getEffectMeta(effectName);
var effectValue = displayEffects[effectName];
if (!isEffectMultiplierEnabled && effectMeta.calculation !== "nonProportional") {
var nextEffectValue = this.getNextEffectValue(model, effectName);
if (nextEffectValue) {
effectValue = nextEffectValue * (model.metadata.on + 1) - effectValue * model.metadata.on;
}
}
var displayParams = this.game.getEffectDisplayParams(effectName, effectValue, false /*showIfZero*/);
//The function might have returned null if this is the type of effect that's supposed to be hidden.
if (displayParams) {
model.effectModels.push(displayParams);
}
}
},
isPrecraftAvailable: function(model){
for (var i in model.prices){
var price = model.prices[i];
var res = this.game.resPool.get(price.name);
if (res.craftable){
return true;
}
}
return false;
},
precraft: function(model){
this.fetchExtendedModel(model);
for (var i in model.priceModels){
var price = model.priceModels[i];
this._precraftRes(price);
}
},
_precraftRes: function(price) {
if (price.children) {
for (var i in price.children) {
this._precraftRes(price.children[i]);
}
}
var res = this.game.resPool.get(price.name);
if (res.craftable && res.name != "wood" && this.game.workshop.getCraft(res.name).unlocked) {
var amt = price.val - res.value;
if (amt > 0) {
var baseAmt = amt / (1 + this.game.getResCraftRatio(res.name));
this.game.workshop.craft(res.name, baseAmt, false /*no undo*/, true /*force all*/);
}
}
}
});
ButtonModernHelper = {
getTooltipHTML : function(controller, model){
controller.fetchExtendedModel(model);
//throw "ButtonModern::getTooltipHTML must be implemented";
var tooltip = dojo.create("div", { className: "tooltip-inner" }, null);
if (model.tooltipName) {
dojo.create("div", {
innerHTML: model.name,
className: "tooltip-divider"
}, tooltip);
}
// description
var descDiv = dojo.create("div", {
innerHTML: controller.getDescription(model),
className: "desc"
}, tooltip);
if (model.metadata && typeof(model.metadata.isAutomationEnabled) == "boolean"){ //undefined or null don't count here
dojo.create("div", {
innerHTML: model.metadata.isAutomationEnabled ? $I("btn.aon.tooltip") : $I("btn.aoff.tooltip"),
className: "desc small" + (model.metadata.isAutomationEnabled ? " auto-on" : " auto-off")
}, tooltip);
}
if (model.metadata && model.metadata.effects &&
model.metadata.effects["cathPollutionPerTickProd"] > 0 &&
controller.game.science.get("chemistry").researched && !controller.game.opts.disablePollution
){
dojo.create("div", {
innerHTML: $I("btn.pollution.tooltip"),
className: "desc small pollution"
}, tooltip);
}
if (model.metadata && model.metadata.almostLimited){
dojo.create("div", {
innerHTML: $I("btn.almostlimited.tooltip"),
className: "desc small almostlimited"
}, tooltip);
}
var prices = model.priceModels;
var effects = model.effectModels;
var flavor = model.flavor;
if (prices && prices != "" || effects || flavor && flavor != ""){
dojo.style(descDiv, "paddingBottom", "8px");
// prices
if (prices && prices.length){
dojo.style(descDiv, "borderBottom", "1px solid gray");
ButtonModernHelper.renderPrices(tooltip, model); //simple prices
}
// effects
if (effects){
ButtonModernHelper.renderEffects(tooltip, effects);
}
// flavor
if (flavor && flavor != "") {
dojo.create("div", {
innerHTML: flavor,
className: "flavor",
style: {
paddingTop: "20px",
fontSize: "12px",
fontStyle: "italic"
}}, tooltip);
}
} else {
dojo.style(descDiv, "paddingBottom", "4px");
}
return tooltip.outerHTML;
},
renderPrices : function(tooltip, model){
var prices = model.priceModels;
if (!prices.length){
return;
}
for( var i = 0; i < prices.length; i++){
var price = prices[i];
var span = ButtonModernHelper._renderPriceLine(tooltip, price);
}
},
_renderPriceLine : function(tooltip, price) {
var priceItemNode = dojo.create("div", {
className: "price-block",
style : {
overflow: "hidden"
}
}, tooltip);
var nameSpan = dojo.create("span", { innerHTML: price.title, style: { float: "left", paddingRight: "10px"} }, priceItemNode );
var priceSpan = dojo.create("span", {
innerHTML: price.displayValue,
className: price.hasResources ? "" : "noRes",
style: {
float: "right"
}
}, priceItemNode );
if (price.children && price.children.length) {
for (var i = 0; i < price.children.length; i++ ) {
var compSpan = this._renderPriceLine(tooltip, price.children[i]);
for (var k = 0; k < price.children[i].indent; ++k) {
compSpan.name.innerHTML = "&nbsp;&nbsp;&nbsp;" + compSpan.name.innerHTML;
}
//mark unrolled price component as raw
compSpan.name.className = "rawRes";
}
}
return {name: nameSpan, price: priceSpan};
},
renderEffects : function(tooltip, effectsList, hideTitle){
if (!effectsList || !effectsList.length) {
return;
}
if (!hideTitle){
dojo.create("div", {
innerHTML: $I("res.effects") + ":",
className: "tooltip-divider" + " resEffectsTxt",
style: {
textAlign: "center",
width: "100%",
borderBottom: "1px solid gray",
paddingBottom: "4px",
marginBottom: "8px"
}}, tooltip);
}
//-----------------------------------------
for (var i = 0; i < effectsList.length; i++) {
var effectModel = effectsList[i];
var nameSpan = dojo.create("div", {
innerHTML: effectModel.displayEffectName + ": " + effectModel.displayEffectValue,
className: "effectName"
}, tooltip);
}
}
};
/*
* Restyled button with slightly more sophisticated tooltip mechanism
*/
dojo.declare("com.nuclearunicorn.game.ui.ButtonModern", com.nuclearunicorn.game.ui.Button, {
afterRender: function(){
dojo.addClass(this.domNode, "modern");
this.renderLinks();
this.attachTooltip(dojo.partial(this.getTooltipHTML(), this.controller, this.model));
this.buttonContent.title = ""; //no old title for modern buttons :V
if (this.model.hasResourceHover) {
dojo.connect(this.domNode, "onmouseover", this,
dojo.hitch( this, function(){
this.game.setSelectedObject(this.getSelectedObject());
}));
dojo.connect(this.domNode, "onmouseout", this,
dojo.hitch( this, function(){
this.game.clearSelectedObject();
}));
}
},
getTooltipHTML: function(){
return ButtonModernHelper.getTooltipHTML;
},
attachTooltip: function(htmlProvider) {
var container = this.domNode;
UIUtils.attachTooltip(this.game, container, 0, 300, htmlProvider);
},
updateTooltip: function(container, tooltip, htmlProvider){
tooltip.innerHTML = dojo.hitch(this, htmlProvider)();
},
renderLinks: function(){
//do nothing, implement me
},
updateLink: function(buttonLink, modelLink) {
if (buttonLink) {
if (!modelLink) { //This only ever happens if I mess around with console commands
dojo.destroy(buttonLink.link);
this.game.ui.render();
return;
}
buttonLink.link.textContent = modelLink.title;
if (modelLink.cssClass) {buttonLink.link.className = modelLink.cssClass;}
if (modelLink.tooltip) {buttonLink.link.title = modelLink.tooltip;}
dojo.style(buttonLink.link, "display", modelLink.visible === undefined || modelLink.visible ? "" : "none");
}
},
getSelectedObject: function(){
return this.model;
}
});
dojo.declare("com.nuclearunicorn.game.ui.BuildingBtnController", com.nuclearunicorn.game.ui.ButtonModernController, {
initModel: function(options) {
var model = this.inherited(arguments);
model.metadata = this.getMetadata(model);
return model;
},
fetchModel: function(options) {
var model = this.inherited(arguments);
model.hasSellLink = this.hasSellLink(model);
model.showSellLink = model.metadata && model.metadata.val && model.hasSellLink;
var self = this;
if (typeof(model.metadata.togglableOnOff) != "undefined") {
model.togglableOnOffLink = {
title: model.metadata.on ? $I("btn.on.minor") : $I("btn.off.minor"),
tooltip: model.metadata.on ? $I("btn.on.tooltip") : $I("btn.off.tooltip"),
cssClass: model.metadata.on ? "bld-on" : "bld-off",
//reserved for mobile
enabled: model.metadata.val > 0,
handler: function(btn){
self.handleTogglableOnOffClick(model);
}
};
}
if (typeof(model.metadata.isAutomationEnabled) == "boolean") {
model.toggleAutomationLink = {
title: model.metadata.isAutomationEnabled ? "A" : "*",
tooltip: model.metadata.isAutomationEnabled ? $I("btn.aon.tooltip") : $I("btn.aoff.tooltip"),
//reserved for mobile
enabled: model.metadata.val > 0,
cssClass: model.metadata.isAutomationEnabled ? "auto-on" : "auto-off",
handler: function(btn){
self.handleToggleAutomationLinkClick(model);
}
};
}
model.togglable = model.metadata.togglable;
if (typeof(model.metadata.on) != "undefined") {
model.on = model.metadata.on;
}
model.hasResourceHover = true;
return model;
},
getMetadata: function(model){
if (this.model.options.building){
var meta = this.game.bld.get(this.model.options.building);
return meta;
}
return null;
},
getEffects: function(model){
return model.metadata.effects;
},
getTotalEffects: function(model){
return model.metadata.totalEffectsCached;
},
getNextEffectValue: function(model, effectName) {
var underlying = model.metadata;
if (!underlying.updateEffects) {
return undefined;
}
underlying.on++;
underlying.updateEffects(underlying, this.game);
this.game.calendar.cycleEffectsBasics(underlying.effects, underlying.name);
var nextEffectValue = underlying.effects[effectName];
underlying.on--;
underlying.updateEffects(underlying, this.game);
//(cycleEffectsBasics is only relevant for space buildings & it will be applied at a later time so we skip it for now)
return nextEffectValue;
},
getDescription: function(model){
var description = model.metadata.description;
return typeof(description) != "undefined" ? description : "";
},
getFlavor: function(model){
var flavor = model.metadata.flavor;
return typeof(flavor) != "undefined" ? flavor : "";
},
hasSellLink: function(model){
return false;
},
//Called whenever we turn the building on or off.
//The function was previously empty, so I repurposed it for possible non-proportional calculations.
metadataHasChanged: function(model) {
var meta = model.metadata;
if (meta.calculateEffects){
meta.calculateEffects(meta, this.game);
this.game.calendar.cycleEffectsBasics(meta.effects, meta.name); //(Only relevant for space buildings)
}
},
off: function(model, amt) {
amt = amt || 1;
var building = model.metadata;
if (amt > building.on){
amt = building.on;
}
if (building.on >= amt){
building.on -= amt;
if(building.stages){
model.metaAccessor.meta.on -= amt; //stage hack
}
this.metadataHasChanged(model);
this.game.upgrade(building.upgrades);
}
},
offAll: function(model) {
var building = model.metadata;
if (building.on){
building.on = 0;
if(building.stages){
model.metaAccessor.meta.on = 0; //stage hack
}
this.metadataHasChanged(model);
this.game.upgrade(building.upgrades);
}
},
on: function(model, amt) {
amt = amt || 1;
var building = model.metadata;
if (amt > building.val - building.on){
amt = building.val - building.on;
}
if (building.on + amt <= building.val ){
building.on += amt;
if(building.stages){
model.metaAccessor.meta.on += amt; //stage hack
}
this.metadataHasChanged(model);
this.game.upgrade(building.upgrades);
}
},
onAll: function(model) {
var building = model.metadata;
if (building.on < building.val) {
building.on = building.val;
if(building.stages){
model.metaAccessor.meta.on = building.val; //stage hack
}
this.metadataHasChanged(model);
this.game.upgrade(building.upgrades);
}
},
/**
* Returns the number of buildings sold.
*/
sell: function(event, model){
var building = model.metadata;
// Allow buildings to override sell button with custom actions
// But, proceed with normal action as well if true returned.
if (building.canSell) {
if(!building.canSell(building, this.game)) {
return 0;
}
}
var start = building.val;
var end = building.val - 1;
if (end > 0 && event && event.shiftKey) { //no need to confirm if selling just 1
end = 0;
if (this.game.opts.noConfirm) {
this.sellInternal(model, end, true /*requireSellLink*/);
return start;
} else {
var self = this;
var amtSold = 0;
this.game.ui.confirm($("sell.all.confirmation.title"), $I("sell.all.confirmation.msg"), function() {
self.sellInternal(model, end, true /*requireSellLink*/);
amtSold = start;
});
return amtSold;
}
} else if (end >= 0) {
this.sellInternal(model, end, true /*requireSellLink*/);
return start - end; //Should be just 1 if you do the algebra
}
},
/**
* Performs the game-logic of selling a building.
* @param model Object representing the building to be sold.
* @param end Number representing when to stop selling the building. Expected to be a nonnegative integer.
* @param requireSellLink Boolean. If true, in between each iteration we check to see if the building has a sell link.
* If the building doesn't have the sell link, we stop selling at that point.
* Note that in order to have a sell link, game.opts.hideSell must be false.
* If this parameter is false, we don't perform such a check & keep selling until we reach end.
* This feature exists so that Order of the Sun upgrades can have additional requirements
* for when they can be sold, but also so that those requirements can be bypassed
* for purposes such as the implementation of the undo feature.
*/
sellInternal: function(model, end, requireSellLink){
//Check input parameters for validity.
if (typeof(requireSellLink) !== "boolean") {
console.warn("Boolean parameter \"requireSellLink\" was not specified, defaulting to false.");
requireSellLink = false;
}
var building = model.metadata;
while (building.val > end) {
this.decrementValue(model);
model.prices = this.getPrices(model);
this.refund(model);
if (requireSellLink && !this.hasSellLink(model)) { //religion upgrades can't sell past 1
break;
}
}
this.game.upgrade(building.upgrades);
this.game.render();
},
decrementValue: function(model) {
var building = model.metadata;
if (building)
{building.val--;}
if (building.on > building.val){
building.on = building.val;
}
},
updateVisible: function(model) {
model.visible = model.metadata.unlocked || this.game.devMode;
},
handleTogglableOnOffClick: function(model) {
var building = model.metadata;
building.on = building.on ? 0 : building.val; //legacy safe switch
this.game.upgrade(building.upgrades);
},
handleToggleAutomationLinkClick: function(model) {
var building = model.metadata;
building.isAutomationEnabled = !building.isAutomationEnabled;
this.game.upgrade({buildings: [building.name]});
}
});
dojo.declare("com.nuclearunicorn.game.ui.BuildingBtn", com.nuclearunicorn.game.ui.ButtonModern, {
sellHref: null,
toggleHref: null,
/**
* Render button links like off/on and sell
*/
renderLinks: function(){
var building = this.model.metadata;
//var sellLinkAdded = false;
if (this.model.showSellLink){
if (!this.sellHref){
this.sellHref = this.addLink({
title: $I("btn.sell.minor"),
handler: function(event) {
this.sell(event);
}
});
//var sellLinkAdded = true;
dojo.addClass(this.domNode, "hasSellLink");
}
}
//--------------- style -------------
if((building.val > 9 || building.name.length > 10) && this.model.hasSellLink) {
//Steamworks and accelerator specifically can be too large when sell button is on
//(tested to support max 99 bld count)
dojo.addClass(this.domNode, "small-text");
}
//--------------- toggle ------------
if (typeof(this.model.togglable) != "undefined" && this.model.togglable){
this.remove = this.addLinkList([
{
id: "off1",
title: "-",
handler: function(){
this.controller.off(this.model);
}
},
{
id: "off25",
title: "-25",
handler: function(){
this.controller.off(this.model,25);
}
},
{
id: "offAll",
title: "-" + $I("btn.all.minor"),
handler: function(){
this.controller.offAll(this.model);
}
}]
);
this.add = this.addLinkList([
{
id: "add1",
title: "+",
handler: function(){
this.controller.on(this.model);
}
},
{
id: "add25",
title: "+25",
handler: function(){
this.controller.on(this.model,25);
}
},
{
id: "add",
title: "+" + $I("btn.all.minor"),
handler: function(){
this.controller.onAll(this.model);
}
}]
);
}
if (this.model.togglableOnOffLink){
this.toggle = this.addLink(this.model.togglableOnOffLink);
}
if (this.model.toggleAutomationLink){
this.toggleAutomation = this.addLink(this.model.toggleAutomationLink);
}
},
sell: function(event){
this.controller.sell(event, this.model);
},
update: function(){
this.inherited(arguments);
//we are calling update before render, panic flee
if (!this.buttonContent){
return;
}
var building = this.model.metadata;
if (building && building.val){
// -------------- sell ----------------
if (this.sellHref){
dojo.style(this.sellHref.link, "display", (building.val > 0) ? "" : "none");
}
//--------------- style -------------
if(building.val > 9) {
dojo.style(this.domNode,"font-size","90%");
}
if (this.toggle || this.remove || this.add) {
dojo.removeClass(this.domNode, "bldEnabled");
dojo.removeClass(this.domNode, "bldlackResConvert");
if (building.lackResConvert) {
dojo.toggleClass(this.domNode, "bldlackResConvert", building.on > 0);
} else {
dojo.toggleClass(this.domNode, "bldEnabled", building.on > 0);
}
}
//--------------- toggle ------------
if (this.add) {
dojo.toggleClass(this.add["add1"].link, "enabled", building.on < building.val);
}
this.updateLink(this.toggle, this.model.togglableOnOffLink);
this.updateLink(this.toggleAutomation, this.model.toggleAutomationLink);
}
}
});
dojo.declare("com.nuclearunicorn.game.ui.BuildingStackableBtnController", com.nuclearunicorn.game.ui.BuildingBtnController, {
defaults: function(){
var result = this.inherited(arguments);
result.simplePrices = false;
result.multiplyEffects = true;
return result;
},
getName: function(model){
var meta = model.metadata;
if (!meta.val) {
return meta.label;
} else if (meta.noStackable){
return meta.label + " " + $I("btn.complete");
} else if (meta.togglableOnOff){
return meta.label + " (" + meta.val + ")";
} else if (meta.togglable) {
//it's not so important h
/*if (meta.val >= 1000){
return meta.label + " (" +
(meta.on < 10000 ? ((meta.on/1000).toFixed(1) + "K") : this.game.getDisplayValueExt(meta.on)) + "/" +
(meta.val < 10000 ? ((meta.val/1000).toFixed(1) + "K") : this.game.getDisplayValueExt(meta.val)) +
")";
}*/
return meta.label + " (" + meta.on + "/" + meta.val + ")";
} else {
return meta.label + " (" + meta.on + ")";
}
},
getPrices: function(model){
var meta = model.metadata;
var ratio = meta.priceRatio || 1;
var prices = [];
var pricesDiscount = this.game.getLimitedDR((this.game.getEffect(meta.name + "CostReduction")), 1);
var priceModifier = 1 - pricesDiscount;
for (var i = 0; i < meta.prices.length; i++){
var resPriceDiscount = this.game.getEffect(meta.prices[i].name + "CostReduction");
resPriceDiscount = this.game.getLimitedDR(resPriceDiscount, 1);
var resPriceModifier = 1 - resPriceDiscount;
prices.push({
val: meta.prices[i].val * Math.pow(ratio, meta.val) * resPriceModifier * priceModifier,
name: meta.prices[i].name
});
}
return prices;
},
updateEnabled: function(model){
this.inherited(arguments);
var meta = model.metadata;
// Beginning with exceptions
if (typeof(meta.limitBuild) == "number" && meta.limitBuild <= meta.val) {
model.enabled = false;
} else if (!meta.on || meta.on && !meta.noStackable) {
// do nothing
} else if (meta.on && meta.noStackable){
model.enabled = false;
}
},
buyItem: function(model, event, callback) {
if (model.enabled && this.hasResources(model) || this.game.devMode) {
var meta = model.metadata;
if (this.game.ironWill && meta.effects && meta.effects["maxKittens"] > 0 && this.game.science.get("archery").researched) {
var self = this;
this.game.ui.confirm("", $I("iron.will.break.confirmation.msg"), function() {
self._buyItem_step2(model, event, callback);
}, function() {
callback(false);
});
} else {
this._buyItem_step2(model, event, callback);
}
} else {
callback(false);
}
},
_buyItem_step2: function(model, event, callback) {
var meta = model.metadata;
if (!meta.noStackable && event.shiftKey) {
var maxBld = 10000;
if (this.game.opts.noConfirm) {
this.build(model, maxBld);
callback(true);
} else {
var self = this;
this.game.ui.confirm($I("construct.all.confirmation.title"), $I("construct.all.confirmation.msg"), function() {
self.build(model, maxBld);
callback(true);
}, function() {
callback(false);
});
}
} else if (!meta.noStackable && (event.ctrlKey || event.metaKey /*osx tears*/)) {
this.build(model, this.game.opts.batchSize || 10);
callback(true);
} else {
this.build(model, 1);
callback(true);
}
},
/**
* Ultimate entry point to building construction
* @param {*} model
* @param {*} maxBld
*
*/
build: function(model, maxBld){
var meta = model.metadata;
var counter = 0;
if (typeof meta.limitBuild == "number" && meta.limitBuild - meta.val < maxBld){
maxBld = meta.limitBuild - meta.val;
}
if (!model.enabled && !this.game.devMode){
return 0;
}
while ((this.game.devMode || this.hasResources(model)) && maxBld > 0){
this.incrementValue(model);
this.payPrice(model);
counter++;
maxBld--;
}
if (!counter){
return 0;
}
if (counter > 1) {
this.game.msg($I("construct.all.msg", [meta.label, counter]), "notice");
}
if (meta.breakIronWill) {
this.game.ironWill = false;
var liberty = this.game.science.getPolicy("liberty");
liberty.calculateEffects(liberty, this.game);
var zebraOutpostMeta = this.game.bld.getBuildingExt("zebraOutpost").meta;
zebraOutpostMeta.calculateEffects(zebraOutpostMeta, this.game);
zebraOutpostMeta.jammed = false;
this.game.diplomacy.onLeavingIW();
}
if (meta.unlocks) {
this.game.unlock(meta.unlocks);
}
if (meta.calculateEffects){
meta.calculateEffects(meta, this.game);
this.game.calendar.cycleEffectsBasics(meta.effects, meta.name); //(Only relevant for space buildings)
}
if (meta.unlockScheme && meta.val >= meta.unlockScheme.threshold) {
this.game.ui.unlockScheme(meta.unlockScheme.name);
}
if (meta.upgrades) {
if (meta.updateEffects) {
meta.updateEffects(meta, this.game);
}
this.game.upgrade(meta.upgrades);
}
return counter;
},
incrementValue: function(model) {
var meta = model.metadata;
var allOff = meta.val > 0 && meta.on == 0;
meta.val++;
meta.on++;
// don't turn on the new building if it's .togglable or .togglableOnOff, you already built at least 1, and all were off before,
// or if it's .togglableOnOff, it's your first, and you don't have paragon
// (because steamworks isn't useful without upgrades so we don't want to confuse new players)
if ((meta.togglableOnOff && (allOff || (meta.val == 1 && this.game.resPool.get("paragon").value == 0))) ||
(meta.togglable && allOff)) {
meta.on--;
}
}
});
dojo.declare("com.nuclearunicorn.game.ui.BuildingStackableBtn", com.nuclearunicorn.game.ui.BuildingBtn, {
onClick: function(event){
this.inherited(arguments);
this.game.render();
}
});
dojo.declare("com.nuclearunicorn.game.ui.BuildingNotStackableBtnController", com.nuclearunicorn.game.ui.BuildingBtnController, {
getDescription: function(model){
var meta = model.metadata;
if (meta.effectDesc && meta.researched){
return this.inherited(arguments) + "<br>" + $I("res.effect") + ": " + meta.effectDesc;
} else {
return this.inherited(arguments);
}
},
getName: function(model){
var meta = model.metadata;
if (meta.researched){
return meta.label + " " + $I("btn.complete.capital");
} else {
return meta.label;
}
},
getPrices: function(model) {
return $.extend(true, [], model.metadata.prices); // Create a new array to keep original values
},
updateEnabled: function(model){
this.inherited(arguments);
if (model.metadata.researched){
model.enabled = false;
}
},
buyItem: function(model, event, callback) {
if ((!model.metadata.researched && this.hasResources(model)) || this.game.devMode){
this.payPrice(model);
this.onPurchase(model);
callback(true);
this.game.render();
return;
}
callback(false);
},
onPurchase: function(model){
var meta = model.metadata;
meta.researched = true;
if (meta.handler){
meta.handler(this.game, meta);
}
if (meta.unlocks) {
this.game.unlock(meta.unlocks);
}
if (meta.upgrades) {
this.game.upgrade(meta.upgrades);
}
}
});
dojo.declare("com.nuclearunicorn.game.ui.BuildingResearchBtn", com.nuclearunicorn.game.ui.BuildingBtn, {
});
dojo.declare("com.nuclearunicorn.game.ui.Spacer", null, {
title: "",
constructor: function(title){
this.title = title;
},
render: function(container){
dojo.create("div", { innerHTML: this.title, className: "spacer"}, container);
},
update: function(){
}
});
dojo.declare("com.nuclearunicorn.game.ui.ContentRowRenderer", null, {
twoRows: false, //by default every tab/panel has one row only
leftRow: null,
rightRow: null,
initRenderer: function(content){
this.content = content;
if (this.twoRows){
var table = dojo.create("table", {
cellpadding: "0",
cellspacing: "0",
style: { width: "100%"}
}, content);
var tr = dojo.create("tr", {}, table);
this.leftRow = dojo.create("td", {style:{verticalAlign: "top"}}, tr);
this.rightRow = dojo.create("td", {style:{verticalAlign: "top"}}, tr);
}
},
/**
* Get a DOM Node container for an array element with a given index, starting with 0
*/
getElementContainer: function(id){
if (!this.twoRows){
return this.content;
}
if (id % 2 == 0){
return this.leftRow;
} else {
return this.rightRow;
}
}
});
dojo.declare("mixin.IGameAware", null, {
game: null,
setGame: function(game){
this.game = game;
}
});
dojo.declare("mixin.IChildrenAware", null, {
children: null,
constructor: function(){
this.children = [];
},
addChild: function (child) {
if (!child) {
throw "Child can't be null";
}
this.children.push(child);
},
render: function(container){
dojo.forEach(this.children, function(e, i){
e.render(container);
});
},
update: function(){
dojo.forEach(this.children, function(e, i){ e.update(); });
}
});
/**
* Collapsible panel for a tab
*/
dojo.declare("com.nuclearunicorn.game.ui.Panel", [com.nuclearunicorn.game.ui.ContentRowRenderer, mixin.IChildrenAware], {
game: null,
collapsed: false,
visible: true,
name: "",
panelDiv: null,
//------ collapse ------
toggle: null,
contentDiv: null,
constructor: function(name, tabManager){
this.name = name;
if (tabManager){
tabManager.registerPanel(name, this);
}
},
render: function(container){
var panel = dojo.create("div", {
className: "panelContainer",
style: {
display: this.visible ? "" : "none"
}
},
container);
this.toggle = dojo.create("div", {
innerHTML: this.collapsed ? "+" : "-",
tabIndex: 0,
className: "toggle" + (this.collapsed ? " collapsed" : ""),
style: {
float: "right"
}
}, panel);
this.title = dojo.create("div", {
innerHTML: this.name,
className: "title"
}, panel);
this.contentDiv = dojo.create("div", {
className: "container",
style: {
display: this.collapsed ? "none" : ""
}
}, panel);
dojo.connect(this.toggle, "onclick", this, function(){
this.collapse(!this.collapsed);
});
dojo.connect(this.toggle, "onkeypress", this, "onKeyPress");
this.panelDiv = panel;
/*
* Render all children, probably not a best thing from architectual point of view
*/
this.inherited(arguments, [this.contentDiv] /* dojo majic */);
return this.contentDiv;
},
onKeyPress: function(event){
if (event.key == "Enter"){
this.collapse(!this.collapsed);
}
},
collapse: function(isCollapsed){
this.collapsed = isCollapsed;
$(this.contentDiv).toggle(!isCollapsed);
this.toggle.innerHTML = isCollapsed ? "+" : "-";
this.onToggle(isCollapsed);
var hasClassCollapsed = dojo.hasClass(this.toggle, "collapsed");
if (isCollapsed && !hasClassCollapsed){
dojo.addClass(this.toggle, "collapsed");
} else if (!isCollapsed && hasClassCollapsed) {
dojo.removeClass(this.toggle, "collapsed");
}
},
onToggle: function(isCollapsed){
//subscribe me!
},
setVisible: function(visible){
this.visible = visible;
if (this.panelDiv){
$(this.panelDiv).toggle(visible);
}
},
update: function(){
this.inherited(arguments);
},
setGame: function(game){
this.game = game;
}
});
/**
* Tab
*/
dojo.declare("com.nuclearunicorn.game.ui.tab", [com.nuclearunicorn.game.ui.ContentRowRenderer, mixin.IChildrenAware], {
game: null,
buttons: null,
tabId: null,
tabName: null,
domNode: null,
visible: true,
constructor: function(opts, game){
this.tabName = opts.name;
this.tabId = opts.id;
this.buttons = [];
this.game = game;
},
render: function(tabContainer){
this.inherited(arguments);
this.initRenderer(tabContainer);
},
update: function(){
this.inherited(arguments);
/*--------------------------
Todo: this stuff is really deprecated, move it to the BLDv2 tab?
---------------------------*/
for (var i = 0; i < this.buttons.length; i++){
var button = this.buttons[i];
button.update();
}
},
updateTab: function(){
},
/*--------------------------
This stuff is deprecated to
---------------------------*/
addButton:function(button){
button.game = this.game;
button.tab = this;
this.buttons.push(button);
}
});
/**
* TODO: return offset from a htmlProvider.
* Ideally it should be some structure like
* {
* x,
* y,
* html
* }
*/
UIUtils = {
attachTooltip: function(game, container, topPosition, leftPosition, htmlProvider) {
var gameNode = dojo.byId("game");
var tooltip = dojo.byId("tooltip");
dojo.connect(container, "onmouseover", this, function() {
game.tooltipUpdateFunc = function(){
tooltip.innerHTML = dojo.hitch(game, htmlProvider)();
};
game.tooltipUpdateFunc();
var pos = $(container).offset();
// Compensate tooltip position for game container offset.
var posGame = $(gameNode).offset();
pos.top -= posGame.top;
pos.left -= posGame.left;
pos.top += topPosition;
pos.left += leftPosition;
// Prevent tooltip from leaving the window area
// The 25 here is an arbitrary padding, so that the tooltip doesn't sit right on the window edge.
var maxTooltipTop = $(window).scrollTop() + $(window).height() - $(tooltip).outerHeight() - 25;
var maxTooltipLeft = $(window).scrollLeft() + $(window).width() - $(tooltip).outerWidth() - 25;
// Keep position inside expected bounds.
pos.top = Math.min(pos.top, maxTooltipTop);
pos.left = Math.min(pos.left, maxTooltipLeft);
dojo.style(tooltip, "top", pos.top + "px");
dojo.style(tooltip, "left", pos.left + "px");
if (tooltip.innerHTML) {
dojo.style(tooltip, "display", "");
}
});
dojo.connect(container, "onmouseout", this, function(){
game.tooltipUpdateFunc = null;
dojo.style(tooltip, "display", "none");
});
return htmlProvider;
}
};
JavaScript
1
https://gitee.com/likexia/cat-zh.git
git@gitee.com:likexia/cat-zh.git
likexia
cat-zh
猫国建设者(Kittens Game)
main

搜索帮助