Tweak physics, cannons, nsbtp support, prepare to add items.

This commit is contained in:
riperiperi committed 2019-05-29 01:08:48 +01:00
1 parent e1dc8603d1
commit e010627b5d
19 files changed
+982 -353

No files matched your search

+12 -8
View File
@@ -36,17 +36,17 @@ window.ObjDecor = function(obji, scene) {
if (t.angle[2] != 0) mat4.rotateZ(mat, mat, t.angle[2]*(Math.PI/180));
if (t.angle[1] != 0) mat4.rotateY(mat, mat, t.angle[1]*(Math.PI/180));
if (t.angle[0] != 0) mat4.rotateX(mat, mat, t.angle[0]*(Math.PI/180));
if (anim != null) {
animMat = anim.setFrame(0, 0, animFrame++);
}
mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
res.mdl[0].draw(mat, pMatrix, animMat);
}
function update() {
res.mdl[0].setFrame(animFrame);
if (anim != null) {
animMat = anim.setFrame(0, 0, animFrame);
}
animFrame++;
}
function requireRes() { //scene asks what resources to load
@@ -75,7 +75,7 @@ window.ObjDecor = function(obji, scene) {
case 0x0138:
return {mdl:[{nsbmd:"GardenTree1.nsbmd"}]};
case 0x0139:
return {mdl:[{nsbmd:"kamome.nsbmd"}], other:[null, null, "kamone.nsbtp"]}; //animates using nsbtp, and uses route to move
return {mdl:[{nsbmd:"kamome.nsbmd"}], other:[null, null, "kamome.nsbtp"]}; //animates using nsbtp, and uses route to move
case 0x013A:
return {mdl:[{nsbmd:"CrossTree1.nsbmd"}]};
@@ -265,8 +265,12 @@ window.ObjDecor = function(obji, scene) {
if (r.other.length > 0 && r.other[0] != null) {
res.mdl[0].loadTexAnim(r.other[0]);
}
if (r.other.length > 1 && r.other[1] != null)
if (r.other.length > 1 && r.other[1] != null) {
anim = new nitroAnimator(r.mdl[0].bmd, r.other[1]);
}
if (r.other.length > 2 && r.other[2] != null) {
res.mdl[0].loadTexPAnim(r.other[2]);
}
}
}
+292
View File
@@ -0,0 +1,292 @@
//
// shell.js
//--------------------
// Entity type for any item. Specific item types in `/item` folder
// Has a default collision handler, but can pass control to the specific item code.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/kcl.js
//
var itemTypes = {
//physics, holdable
'$koura_g': GreenShellC,
'$koura_r': RedShellC,
'$banana': BananaC,
'$bomb': BombC,
'$f_box': FakeBoxC,
//groups
'$koura_group': ShellGroupC,
'$banana_group': BananaGroupC,
//one use items
'$kinoko': MushroomC,
'$kinoko_group': MushroomGroupC,
'$kinoko_p': QueenMushroomC,
'$star': StarC,
'$thunder': ThunderC,
'$gesso': BlooperC,
'$teresa': BooC,
'$killer': KillerC,
'$koura_w': BlueShellC
}
window.Item = function(scene, owner, type) {
var t = this;
var minimumMove = 0.01;
this.id = 0;
this.pos = vec3.transformMat4([], [0, (-owner.params.colRadius)+1, 16], owner.mat);
this.vel = vec3.create();
this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds
this.minBounceVel = 0.5;
this.airResist = 0.95;
this.enablePhysics = true;
this.floorBounce = 0.5;
this.held = true;
this.type = type;
this.owner = owner;
this.angle = owner.angle;
this.speed = 10;
this.yvel = 0;
this.colRadius = 4;
this.holdDist = 16;
this.safeKart = owner;
var deadTimerLength = 20;
var throwVelocity = 16;
var throwAngle = (Math.PI / 3) * 2;
this.deadTimer = 0; //animates death. goes to 20, then deletes for real. dead objects can't run update or otherwise
//a controller makes this item what it is...
// canBeHeld: boolean
// canBeDropped: boolean | 'func'
// isDestructive: boolean
// update?: (scene: CourseScene) => void
// draw?: (mvMatrix, pMatrix) => void // OVERRIDES NORMAL DRAW FUNCTION!
// release?: (direction: number) => boolean //direction is 1 for forward, -1 for back. returns if the item has more uses
// collide?: (item: Item | Kart)
// collideKart?: (item: Kart)
var subtypeInd = type.indexOf('-');
if (subtypeInd == -1) subtypeInd = type.length;
this.controller = new itemTypes["$"+type.substr(0, subtypeInd)](this, scene, type.substr(subtypeInd + 1));
//functions
this.update = update;
this.draw = draw;
this.updateHold = updateHold;
this.release = release;
this.canBeHeld = canBeHeld;
this.canBeDropped = canBeDropped;
this.isDestructive = isDestructive;
this.isSolid = isSolid;
this.finalize = finalize;
this.collide = collide;
function updateHold(kart) {
//move the object behind the kart (physical direction without drift off)
//assuming this will only be called for something that can be held
var dir = -kart.driftOff;
//offset the kart's drift offset (on direction)
var pos = [Math.sin(dir)*t.holdDist, 0, -Math.cos(dir)*t.holdDist];
//make relative to the kart's position
vec3.transformMat4(pos, pos, kart.mat);
vec3.sub(t.vel, pos, t.pos); //set the object's velocity to try move it to the hold location. (gravity is disabled)
t.enablePhysics = true;
}
function release(forward) {
//release the item, either forward or back
if (t.canBeHeld()) t.updateHold(owner);
if (t.controller.release) t.controller.release(forward);
else {
//default drop and throw. just here for template purposes
if (forward >= 0) {
var dir = owner.physicalDir;
vec3.zero(t.vel);
} else {
vec3.zero(t.vel);
}
}
this.held = false;
}
function canBeHeld() {
return t.controller.canBeHeld || false;
}
function canBeDropped() {
return t.controller.canBeDropped || true;
}
function isDestructive() {
return t.controller.isDestructive || false;
}
function isSolid() {
return t.controller.isSolid || true;
}
function finalize() {
//kill instantly
t.deadTimer = deadTimerLength;
scene.items.removeItem(t);
}
function collide(item) {
if (t.controller.collide) {
t.controller.collide(item);
return;
}
if (item.type) {
//has a type, definitely an item
if (item.isDestructive() || t.isDestructive()) {
//mutual destruction. other side will deal with how they handle the collision
t.deadTimer++;
} else if (item.isSolid() && t.isSolid()) {
//bounce off other items that are not destructive
//set our velocity to move away (not too intensely)
//(only apply if our id is before, to avoid double adding the velocity)
if (t.id < item.id) {
var diff = vec3.sub([], t.pos, item.pos);
vec3.scale(diff, diff, 0.5);
vec3.add(t.vel, t.vel, diff);
vec3.sub(item.vel, item.vel, diff);
t.enablePhysics = true;
item.enablePhysics = true;
}
}
} else {
//is a kart. usually this is where objects differ
if (t.controller.collideKart) {
t.controller.collideKart(item);
}
}
}
function update(scene) {
if (t.controller.update) t.controller.update(scene);
if (t.deadTimer > 0) {
t.deadTimer++;
if (t.deadTimer >= 20) t.finalize();
return;
}
//search for player collisions, collisions with other items
for (var i=0; i<scene.karts.length; i++) {
var ok = scene.karts[i];
var dist = vec3.dist(vec3.add([], t.pos, [0,1,0]), ok.pos);
if (dist < t.colRadius + 12) {
//colliding with a kart.
//do we need to do something?
t.collide(ok);
}
}
for (var i=0; i<scene.items.length; i++) {
var ot = scene.items[i];
var dist = vec3.dist(t.pos, ot.pos);
if (dist < t.colRadius + ot.colRadius) {
//two items are colliding.
t.collide(ot);
}
}
if (t.enablePhysics) {
if (!t.held) {
vec3.add(t.vel, t.vel, t.gravity);
vec3.scale(t.vel, t.vel, t.airResist);
}
//by default, items use raycast collision against the world (rather than ellipse)
//this speeds things up considerably
var steps = 0;
var remainingT = 1;
var velSeg = vec3.clone(t.vel);
var posSeg = vec3.clone(t.pos);
var ignoreList = [];
while (steps++ < 10 && remainingT > 0.01) {
var result = lsc.raycast(posSeg, velSeg, scene.kcl, 0.05, ignoreList);
if (result != null) {
if (t.controller.colResponse) t.controller.colResponse(posSeg, velSeg, result, ignoreList)
else colResponse(posSeg, velSeg, result, ignoreList)
remainingT -= result.t;
if (remainingT > 0.01) {
velSeg = vec3.scale(vec3.create(), t.vel, remainingT);
}
} else {
vec3.add(posSeg, posSeg, velSeg);
remainingT = 0;
}
}
t.pos = posSeg;
}
}
function draw(mvMatrix, pMatrix) {
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1-(t.deadTimer/deadTimerLength)]); //fade out
if (t.controller.draw) {
t.controller.draw(mvMatrix, pMatrix);
} else {
var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), t.pos, [0, 3, 0]));
spritify(mat);
mat4.scale(mat, mat, [16, 16, 16]);
scene.gameRes.items[type].draw(mat, pMatrix);
}
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1]);
}
var spritify = function(mat, scale) {
var scale = (scale == null)?Math.sqrt(mat[0]*mat[0]+mat[1]*mat[1]+mat[2]*mat[2]):scale;
mat[0]=scale; mat[1]=0; mat[2]=0;
mat[4]=0; mat[5]=scale; mat[6]=0;
mat[8]=0; mat[9]=0; mat[10]=scale;
}
function colResponse(pos, pvel, dat, ignoreList) {
var plane = dat.plane;
var colType = (plane.CollisionType>>8)&31;
vec3.add(pos, pos, vec3.scale(vec3.create(), pvel, dat.t));
var n = dat.normal;
vec3.normalize(n, n);
var adjustPos = true;
if (MKDS_COLTYPE.GROUP_WALL.indexOf(colType) != -1) { //wall
//normally, item collision with a wall cause a perfect reflection of the velocity.
var proj = vec3.dot(t.vel, n) * 2;
vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj));
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane
var proj = vec3.dot(t.vel, n) * (1 + t.floorBounce);
vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj));
if (t.floorBounce == 0 || Math.abs(proj) < t.minBounceVel) t.enablePhysics = false;
} else {
adjustPos = false;
ignoreList.push(plane);
}
if (adjustPos) { //move back from plane slightly
vec3.add(pos, pos, vec3.scale(vec3.create(), n, minimumMove));
}
}
}
+1
View File
@@ -48,6 +48,7 @@ window.ItemBox = function(obji, scene) {
scene.particles.push(new NitroEmitter(scene, ok, 47));
t.mode = 1;
t.time = 0;
ok.items.getItem(null); //todo: specific item from some
break;
}
}
+54
View File
@@ -0,0 +1,54 @@
window.BananaC = function(item, scene, type) {
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
item.minBounceVel = 0;
this.collideKart = collideKart;
function collideKart(kart) {
item.deadTimerLength = 20;
kart.damage(MKDSCONST.DAMAGE_SPIN);
}
}
window.BananaGroupC = function(item, scene, type) {
this.canBeHeld = false;
this.canBeDropped = 'func';
this.rotationPeriod = 45;
item.colRadius = -Infinity;
item.enablePhysics = false;
this.draw = draw;
function draw(mvMatrix, pMatrix) {
//the group itself is invisible - the bananas draw individually
}
}
window.FakeBoxC = function(item, scene, type) {
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
this.isSolid = true;
var model = scene.gameRes.fakeBox;
this.draw = draw;
function draw(view, pMatrix) {
mat4.translate(mat, view, t.pos);
mat4.translate(mat, view, [0, 16, 0]);
/* adjust to make it rest on a corner
if (t.angle[2] != 0) mat4.rotateZ(mat, mat, t.angle[2]*(Math.PI/180));
if (t.angle[1] != 0) mat4.rotateY(mat, mat, t.angle[1]*(Math.PI/180));
if (t.angle[0] != 0) mat4.rotateX(mat, mat, t.angle[0]*(Math.PI/180));
*/
mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
model.draw(mat, pMatrix, animMat);
}
}
window.BombC = null;
+10
View File
@@ -0,0 +1,10 @@
//boost, 3x boost, queen boost, star, ghost
window.MushroomC = null;
window.MushroomGroupC = null;
window.QueenMushroomC = null;
window.StarC = null;
window.ThunderC = null;
window.BlooperC = null;
window.BooC = null;
window.KillerC = null;
+25
View File
@@ -0,0 +1,25 @@
window.GreenShellC = function(item, scene) {
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = true;
}
window.RedShellC = function(item, scene) {
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = true;
}
window.ShellGroupC = function(item, scene, type) {
this.canBeHeld = false;
this.canBeDropped = 'func';
this.rotationPeriod = 45;
this.draw = draw;
function draw(mvMatrix, pMatrix) {
//the group itself is invisible - the shells draw individually
}
}
window.BlueShellC = null;
+55 -9
View File
@@ -27,6 +27,9 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
this.active = true;
this.preboost = true;
//supplimentary controllers
this.items = new KartItems(this, scene);
this.soundProps = {};
this.pos = pos;
this.angle = angle;
@@ -311,6 +314,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
kartAnim = (kartAnim+1)%8;
var input = k.controller.fetchInput();
k.lastInput = input;
k.items.update(input);
if (input.turn > 0.3) {
if (k.driveAnimF < 28) k.driveAnimF++;
@@ -386,7 +390,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
} else if (k.cannon != null) { //when cannon is active, we fly forward at max move speed until we get to the cannon point.
var c = scene.nkm.sections["KTPC"].entries[k.cannon];
if (c.id2 != 0) {
if (c.id1 != -1 && c.id2 != -1) {
var c2 = scene.nkm.sections["KTPC"].entries[c.id2];
c = c2;
@@ -401,10 +405,18 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.angle = k.physicalDir;
k.cannon = null;
} else {
var mat = mat4.create();
mat4.rotateY(mat, mat, c.angle[1]*(Math.PI/180));
mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180));
if (true) {
//vertical angle from position? airship fortress is impossible otherwise
//var c2 = scene.nkm.sections["KTPC"].entries[c.id2];
var diff = vec3.sub([], c.pos, k.pos);
var dAdj = Math.sqrt(diff[0]*diff[0] + diff[2]*diff[2]);
var dHyp = Math.sqrt(diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2]);
mat4.rotateX(mat, mat, ((diff[1] > 0) ? -1 : 1) * Math.acos(dAdj/dHyp));
} else {
mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180));
}
var forward = [0, 0, 1];
var up = [0, 1, 0];
@@ -415,10 +427,15 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.physicalDir = (180-c.angle[1])*(Math.PI/180);
k.angle = k.physicalDir;
k.kartTargetNormal = vec3.transformMat4(up, up, mat);
k.airTime = 0;
var planeConst = -vec3.dot(c.pos, forward);
var cannonDist = vec3.dot(k.pos, forward) + planeConst;
if (cannonDist > 0) k.cannon = null;
if (cannonDist > 0) {
k.cannon = null; //leaving cannon state
k.speed = params.topSpeed;
k.vel = vec3.scale([], vec3.transformMat4(forward, forward, mat), k.speed);
}
}
} else { //default kart mode
@@ -611,6 +628,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.driftLanded = false;
k.driftMode = 0;
k.ylock = 0;
onGround = false;
var boing = nitroAudio.playSound(207, {transpose: -4}, 0, k);
boing.gainN.gain.value = 2;
@@ -655,11 +673,18 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
vec3.add(k.vel, k.vel, k.kartColVel);
}
} else {
k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling/2;
k.angle = fixDir(k.physicalDir);
k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling;
k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling; //applying this twice appears to be identical to the original
k.angle = fixDir(k.angle);
//reduce our forward speed by how much of our velocity is not going forwards
var factor = Math.sin(k.physicalDir)*Math.sin(k.angle) + Math.cos(k.physicalDir)*Math.cos(k.angle);
k.speed *= 1 - ((1-factor) * (1 - k.params.decel));
//var reducedSpeed = k.vel[0]*Math.sin(k.angle) + k.vel[2]*(-Math.cos(k.angle));
//reducedSpeed = ((reducedSpeed < 0) ? -1 : 1) * Math.sqrt(Math.abs(reducedSpeed));
k.vel[1] += k.gravity[1];
k.vel = [Math.sin(k.angle)*k.speed, k.vel[1], -Math.cos(k.angle)*k.speed]
//k.speed = reducedSpeed;
if (k.kartColTimer > 0) {
vec3.add(k.vel, k.vel, vec3.scale([], k.kartColVel, k.kartColTimer/10))
@@ -738,6 +763,24 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
positionChanged(lastPos, k.pos);
}
function triggerCannon(id) {
if (k.cannon != null) return;
k.cannon = id;
var c = scene.nkm.sections["KTPC"].entries[k.cannon];
if (c.id1 != -1 && c.id2 != -1) {
nitroAudio.playSound(345, {volume: 2.5}, 0, k);
} else {
nitroAudio.playSound(347, {volume: 2.5}, 0, k);
if (k.local) {
if (c.id2 == 0) {
nitroAudio.playSound(380, {volume: 2}, 0, null); //airship fortress
} else {
nitroAudio.playSound(456, {volume: 2}, 0, null); //waluigi
}
}
}
}
function playCharacterSound(sound, volume) {
//0 - hit
//1 - hit spin
@@ -856,7 +899,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
if (k != ok) {
var dist = vec3.dist(k.pos, ok.pos);
if (dist < 16) {
nitroAudio.playSound(208, { volume: 2 }, 0, k);
kartBounce(ok);
ok.kartBounce(k);
}
@@ -879,6 +922,8 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
if (vec3.length(k.vel) < vec3.length(ok.vel)) vec3.add(k.kartColVel, k.kartColVel, vec3.sub([], ok.vel, k.vel));
k.kartColVel[1] = 0;
//play this kart's horn
nitroAudio.playSound(192 + charRes.sndOff/14, { volume: 2 }, 0, k);
}
function fixDir(dir) {
@@ -896,7 +941,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
function updateKartSound(mode, input) {
var turn = (onGround && !k.drifting)?(1-Math.abs(input.turn)/11):1;
var transpose = (mode == 0)?0:(22*turn*k.speed/params.topSpeed);
var transpose = (mode == 0)?0:(22*turn*Math.min(1.3, k.speed/params.topSpeed));
sounds.transpose += (transpose-sounds.transpose)/15;
if (mode != soundMode) {
@@ -965,6 +1010,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.physBasis = {
mat: m4,
inv: mat4.invert([], m4),
normal: normal,
time: 15,
loop: false
};
@@ -1099,7 +1145,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
stuckTo = dat.object;
} else if (colType == MKDS_COLTYPE.CANNON) {
//cannon!!
k.cannon = colBE;
triggerCannon(colBE);
} else {
adjustPos = false;
ignoreList.push(plane);
+77
View File
@@ -0,0 +1,77 @@
//item state for a kart. not an entity, just supplemental to one.
window.KartItems = function(kart, scene) {
var t = this;
t.heldItem = null; //of type Item
t.currentItem = null; //string name for item
t.specificItem = null;
t.empty = true;
t.cycleTime = 0;
t.totalTime = 230;
var maxItemTime = 230;
var minItemTime = 80;
var carouselSfx = null;
var lastItemState = false;
var holdAppearDelay = 15;
var hurtExplodeDelay = 105 //turn right slightly, huge double backflip, small bounces.
var hurtFlipDelay = 80; //turn right slightly, bounce twice, forward flip
var hurtSpinDelay = 40; //counter clockwise spin
t.getItem = getItem;
t.update = update;
var specialItems = ["star"];
function sfx(id) {
if (kart.local) {
return nitroAudio.playSound(id, {volume: 2}, 0, null);
}
return null;
}
function getItem(specific) {
if (!t.empty) return false;
else {
//begin carousel
t.cycleTime = 0;
t.totalTime = (specific) ? 60 : maxItemTime;
if (specific) t.specificItem = specific;
t.empty = false;
carouselSfx = sfx(62);
}
}
function update(input) {
var pressed = (input.item && !lastItemState);
if (!t.empty) {
if (t.currentItem == null) {
//carousel
t.cycleTime++;
if (t.cycleTime >= t.totalTime) {
if (carouselSfx != null) nitroAudio.kill(carouselSfx);
//decide on an item
var item = "koura_g";
sfx((specialItems.indexOf(item) == -1) ? 63 : 64);
t.currentItem = item;
} else {
//if item button is pressed, we speed up the carousel
if (pressed) {
t.totalTime = Math.max(minItemTime, t.totalTime - 20);
}
}
} else {
if (pressed) {
//fire?
t.currentItem = null;
t.empty = true;
kart.playCharacterSound(7);
}
}
}
lastItemState = input.item;
}
}