waves/public/assets/g/wbwwb/js/game/Director.js
2025-04-09 17:11:14 -05:00

505 lines
11 KiB
JavaScript

/**************************************
THE DIRECTOR: SINGLETON
When you click to take a photo, the Director...
- gets what's inside the camera's bounds
- makes the camera take the texture shot
- controls the movement of the camera & viewport
- decides what the chyron should say
- decides which TV to put it in
- decides what other events should happen around the world
**************************************/
Game.addToManifest({
crickets: "sounds/crickets.mp3",
breaking_news: "sounds/breaking_news.mp3"
});
function Director(scene){
var self = this;
// Scene
self.scene = scene;
// Stages
self.callbacks = {};
self.callback = function(callbackName){
var cb = self.callbacks[callbackName];
if(cb) cb(self);
};
// TAKE PHOTO
self.photoData = null;
self.photoTexture = null;
self.isWatchingTV = false;
self.takePhoto = function(camera){
// Get a photo texture at Camera's position
self.photoTexture = camera.photoTexture;
// Which tv...
self.tv = scene.tv;
// Animation!
Tween_get(self).wait(_s(0.25)) // flash
.call(_anim.movePhoto).wait(_s(BEAT)) // tween takes 1 sec
.call(_anim.cutToTV).wait(_s(BEAT*1.5)) // stay on TV for 1.5 secs
.call(_anim.zoomOut1).wait(_s(BEAT*2)) // tween takes 2 secs
.wait(_s(BEAT*1.5)) // see viewers (or lack thereof) for 1.5 sec
.call(_anim.zoomOut2).wait(_s(BEAT*2)) // tween takes 2 secs
.call(_anim.reset);
// CALLBACK
self.chyron = "[NO CHYRON]";
self.photoData = {};
self.photoData.audience = 0;
self.callback("takePhoto");
};
// ANIM CONSTANTS
Director.ZOOM_OUT_1_TIME = 2;
Director.SEE_VIEWERS_TIME = 1.5 + 0.5; // hack.
Director.ZOOM_OUT_2_TIME = 2;
/////////////////////////////////
///// AUDIENCE & TV HELPERS /////
/////////////////////////////////
self.audience_movePhoto = function(){
var data = self.photoData;
if(self.noSounds) return;
if(data.audience>0 || data.audienceCircles>0 || data.audienceSquares>0){
Game.sounds.breaking_news.play();
}
if(data.forceChyron){
Game.sounds.breaking_news.play();
}
};
self.audience_cutToTV = function(doSomethingElse, filterAudience){
// AUDIENCE?
var data = self.photoData;
var circlesLeft = data.audience;
var squaresLeft = data.audience;
if(data.HIJACK){
circlesLeft = data.audienceCircles;
squaresLeft = data.audienceSquares;
data.audience = data.audienceCircles+data.audienceSquares;
}else{
data.audienceCircles = data.audience;
data.audienceSquares = data.audience;
}
var peeps = self.scene.world.peeps.slice(0); // clone
// GET OUT THE WAY (AGAIN)
for(var i=0;i<peeps.length;i++){
var p = peeps[i];
if(p._CLASS_!="NormalPeep") continue;
p.getOuttaTV();
}
// ANY AUDIENCE, AT ALL?????
if(data.audience>0){
// Who to watch TV, now?
if(filterAudience) peeps=peeps.filter(filterAudience); // filter
peeps.sort(function(){ return Math.random()<0.5; }); // shuffle
for(var i=0;i<peeps.length;i++){
var p = peeps[i];
if(p._CLASS_!="NormalPeep") continue;
// Circles on the left, Squares to the right, and here I am!
var flip, offset;
var watchTV = false;
if(p.type=="circle" && circlesLeft>0){
flip = 1;
offset = 60 + (data.audienceCircles-circlesLeft)*40;
watchTV = true;
circlesLeft--;
}
if(p.type=="square" && squaresLeft>0){
flip = -1;
offset = 60 + (data.audienceSquares-squaresLeft)*40;
watchTV = true;
squaresLeft--;
}
// Watch the TV? Otherwise move, get out the way.
if(watchTV){
p.x = self.tv.x;
p.x -= flip*offset;
p.y = self.tv.y+Math.random(); // tiny offset to avoid glitchy depth-sort
if(doSomethingElse){
doSomethingElse(p);
}else{
p.watchTV();
}
}
}
}
// Did anyone watch?
return (data.audience>0);
};
////////////////////////////////
///// CAM BOUNDS ///////////////
////////////////////////////////
// EVEN BETTER DECLARATIVE HELPER
self.tryChyron = function(catchemFunc){
// Did it catch 'em?
var caught = catchemFunc(self);
// "otherwise" chaining
// if caught something, DO NO MORE. else, keep trying!
if(caught){
var blankChain = {
otherwise: function(){ return blankChain; }
};
return blankChain;
}else{
return {
otherwise: self.tryChyron
};
}
};
self.tryCut2TV = self.tryChyron; // SAME DAMN THING.
// Declarative Helper or whatever
// LIKE THIS:
/*******
var caught = d.caught({
peeps: {_CLASS_:"NormalPeep"},
shocked: {_CLASS_:"NormalPeep", shocked:true}
crazy: {_CLASS_:"CrazyPeep"},
tv: {_CLASS_:"TV"}
});
*******/
self.caught = function(catchem){
var caught = {};
for(var selector in catchem){
var properties = catchem[selector];
var returnAll = properties["returnAll"];
var caughtProps = self.getPropsInCamera(function(prop){
// It fits all the properties...
var isSelected = true;
for(var key in properties){
// Forget this one
if(key=="returnAll") continue;
// Test all keys
var value = properties[key];
if(prop[key]!=value){
isSelected = false;
}
}
return isSelected;
});
caught[selector] = returnAll ? caughtProps : caughtProps[0]; // return all or one?
}
return caught;
};
// Get everything that's at least 33% inside the camera frame
self.getPropsInCamera = function(filterFunc){
// Those caught in the camera
var caught = [];
// cam top-left-right-bottom
var cam = scene.camera;
var cl = cam.x-cam.width/2;
var cr = cam.x+cam.width/2;
var ct = cam.y-cam.height/2;
var cb = cam.y+cam.height/2;
for(var i=0;i<scene.world.props.length;i++){
var prop = scene.world.props[i];
// prop's top-left-right-bottom
var realY = prop.y;
if(prop.z!==undefined) realY+=prop.z;
var pl = prop.x-prop.width/2;
var pr = prop.x+prop.width/2;
var pt = realY-prop.height;
var pb = realY;
var totalArea = prop.width*prop.height;
// not overlapping at all
if(pr<cl) continue;
if(pl>cr) continue;
if(pb<ct) continue;
if(pt>cb) continue;
// overlapping a little bit... but how much?
var l = Math.max(cl,pl);
var t = Math.max(ct,pt);
var r = Math.min(cr,pr);
var b = Math.min(cb,pb);
var overlapArea = (r-l)*(b-t);
var overlapRatio = overlapArea/totalArea;
// If 33% or more, yes!
if(overlapRatio>0.33){
caught.push(prop);
}
}
// Filter?...
if(filterFunc){
caught = caught.filter(filterFunc);
}
return caught;
};
////////////////////////////////
///// ANIMATION ////////////////
////////////////////////////////
var _anim = {};
_anim.movePhoto = function(){
var cam = scene.camera;
// Pan: Center
Tween_get(cam.graphics)
.to({ x:Game.width/2, y:Game.height/2 }, _s(BEAT), Ease.circInOut);
// Scale: fullscreen
var scale = Game.width/cam.width;
Tween_get(cam.graphics.scale)
.to({ x:scale, y:scale }, _s(BEAT), Ease.circInOut);
// CALLBACK
self.callback("movePhoto");
};
_anim.cutToTV = function(){
// YUP, WATCHING TV.
self.isWatchingTV = true;
// Hide Camera
scene.camera.hide();
// SOUND?
var data = self.photoData;
var fail = false;
var nothing = data.ITS_NOTHING;
if(!data.forceChyron){
if(data.audience==0 && !data.audienceCircles && !data.audienceSquares){
Game.sounds.crickets.play();
fail = true;
// SHOW CRICKET
var cricket = new Cricket(scene);
cricket.watchTV();
scene.world.addProp(cricket);
// OR... MULTIPLE CRICKETS!
if(data.CAUGHT_A_CRICKET){
var cricket = new Cricket(scene);
cricket.watchTV();
cricket.x += 30;
cricket.hopAwayTimeout += 15;
scene.world.addProp(cricket);
var cricket = new Cricket(scene);
cricket.watchTV();
cricket.x += 60;
cricket.hopAwayTimeout += 30;
scene.world.addProp(cricket);
}
}
}
// Add photo texture to the TV
var text = self.chyron;
var tv = self.tv;
tv.placePhoto({
photo: self.photoTexture,
text: text,
fail: fail,
nothing: nothing
});
// Where to cut viewport to
self.cutViewportTo({
x: tv.x + tv.offset.x,
y: tv.y + tv.offset.y,
scale: tv.offset.scale
});
// GET OUT THE WAY
var peeps = self.scene.world.peeps.slice(0);
for(var i=0;i<peeps.length;i++){
var p = peeps[i];
if(p._CLASS_!="NormalPeep") continue;
p.getOuttaTV();
}
};
_anim.zoomOut1 = function(){
// Zoom out to see viewer(s)
var tv = self.tv;
var x = (tv.x*3 + Game.width/2)/4;
var y = tv.y + tv.offset.y*3/4;
self.tweenViewportTo({
x: x,
y: y,
scale: 2.5
}, _s(BEAT*2), Ease.cubicInOut);
// CALLBACK - HACK. WHATEVER.
self.callback("cutToTV");
// CALLBACK
self.callback("zoomOut1");
};
_anim.zoomOut2 = function(){
// NOPE, NO LONGER WATCHING TV.
self.isWatchingTV = false;
// Zoom out to the whole game
self.tweenViewportTo({
x: Game.width/2,
y: Game.height/2,
scale: 1
}, _s(BEAT*2), Ease.cubicInOut);
// CALLBACK
self.callback("zoomOut2");
};
_anim.reset = function(){
// Reset Camera
scene.camera.reset();
// World
self.cutViewportTo({
x: Game.width/2,
y: Game.height/2,
scale: 1
});
// CALLBACK
self.callback("reset");
};
// VIEW PORT
self.cutViewportTo = function(view){
var g = scene.world.graphics;
g.pivot.x = view.x;
g.pivot.y = view.y;
g.scale.x = g.scale.y = view.scale;
};
self.tweenViewportTo = function(view, time, easing){
var sl, st, sr, sb; // (S)tarting left, top, right, bottom
var el, et, er, eb; // (E)nding left, top, right, bottom
// Get the starting numbers
var g = scene.world.graphics;
sl = g.pivot.x - ((Game.width/2)/g.scale.x);
sr = g.pivot.x + ((Game.width/2)/g.scale.x);
st = g.pivot.y - ((Game.height/2)/g.scale.y);
sb = g.pivot.y + ((Game.height/2)/g.scale.y);
// Get the destination numbers
el = view.x - ((Game.width/2)/view.scale);
er = view.x + ((Game.width/2)/view.scale);
et = view.y - ((Game.height/2)/view.scale);
eb = view.y + ((Game.height/2)/view.scale);
// TWEEN PARAMETRIC
var tween = {t:0};
Tween_get(tween)
.to({t:1}, time, easing)
.call(function(){
self.update = function(){};
});
// MANUAL TWEEN
self.update = function(){
// Parametric
var t = tween.t;
t = t*t; // coz geometric
// (D)eltas of left, top, right, bottom
var dl = el-sl;
var dt = et-st;
var dr = er-sr;
var db = eb-sb;
// (P)arametric left, top, right, bottom
var pl = sl + dl*t;
var pt = st + dt*t;
var pr = sr + dr*t;
var pb = sb + db*t;
// Convert to pivot & scale
var x = (pl + pr)/2; // middle of left & right
var y = (pt + pb)/2; // middle of top & bottom
var s = Game.width/(pr-pl); // Game.width % current width
// Container
var g = scene.world.graphics;
g.scale.x = g.scale.y = s;
g.pivot.x = x;
g.pivot.y = y;
};
};
// Goof.
self.update = function(){};
}