content/hifi-content/wadewatts/explodingGnomeEntity.js
2022-02-14 02:04:11 +01:00

421 lines
No EOL
17 KiB
JavaScript

(function() {
var _this = null; // will be initialized on preload
const DEBUG = false;
const AVATAR_ENTITIES = false;
const GNOME_CONTENT_PREFIX = 'https://mpassets.highfidelity.com/31479af7-94b0-45f2-84ba-478d27e5af90-v1/';
const GNOME_DEBRIS_PREFIX = GNOME_CONTENT_PREFIX + 'ShatteredPieces/';
// Explosion sound from http://freesound.org/people/harpoyume/sounds/86026/ under CC BY-NC 3.0
const EXPLOSION_SOUND_URL = GNOME_CONTENT_PREFIX + '86026__harpoyume__explosion-3.wav';
// Laugh sound from http://freesound.org/people/thanvannispen/sounds/9557/ under CC BY 3.0
const LAUGH_SOUND_URL = GNOME_CONTENT_PREFIX + '9557__thanvannispen__2gnomes.wav?v3';
const FIRECRACKLING_SOUND_URL = GNOME_CONTENT_PREFIX + '49147__smidoid__fire48.wav';
const SMOKE_TEXTURE_URL = GNOME_CONTENT_PREFIX + 'Particle-Sprite-Smoke-1.png';
const EXPLOSION_TEXTURE_URL = GNOME_CONTENT_PREFIX + 'explode.png';
const PI = 3.141593;
const DEG_TO_RAD = PI / 180.0;
const BLAST_POWER = 5.0;
const THROW_SPEED_TRESHOLD = 2.0; // at least 2m/s
const MIN_BULLET_IMPACT_SPEED = 1.0;
const AVATAR_THRUST_MULITPLIER = 20;
const SPIN_RATE = 20.0;
const LEFT_HAND = 0;
const RIGHT_HAND = 1;
const BOTH_HANDS = 2;
// Blast radius in meters
const BLAST_RADIUS = 6.5;
const BLAST_FALLOFF = 1.8;
const DEBRIS_LIFETIME = 60;
const DEBRIS_BURN_TIME = DEBRIS_LIFETIME * 1000; // 15 seconds
const SMOKE_LIFETIME = 30;
const DEBRIS_PARTS = [
{
model: GNOME_DEBRIS_PREFIX + 'gnomeChunk1.fbx?v2',
offset: {x: 0.002, y: 0.1, z: 0}
},
{
model: GNOME_DEBRIS_PREFIX + 'gnomeChunk2.fbx?v2',
offset: {x: -0.002, y: 0.2, z: 0}
},
{
model: GNOME_DEBRIS_PREFIX + 'gnomeChunk3.fbx?v2',
offset: {x: 0, y: 0.1, z: 0}
},
{
model: GNOME_DEBRIS_PREFIX + 'gnomeChunk4.fbx?v2',
offset: {x: 0.1, y: 0, z: 0}
},
{
model: GNOME_DEBRIS_PREFIX + 'gnomeChunk5.fbx?v2',
offset: {x: 0, y: -0.04, z: 0.1}
}
];
const DETONATION_MODE_IMPACT = 'impact';
const DETONATION_MODE_TIMED = 'timed';
var debugPrint = function(message) {
if (DEBUG) {
print(message);
}
}
// define prototype WeaponizedGnome
function WeaponizedGnome() {
return;
}
WeaponizedGnome.prototype = {
entityID: null,
avatarImpactChannel: null,
explosionSound: null,
laughSound: null,
laughSoundInjector: null,
cracklingSound: null,
smokeTexture: null,
explosionTexture: null,
exploding: false,
thrown: false,
hand: null,
timerEnabled: false,
rezzedDebris: null,
detonationMode: DETONATION_MODE_IMPACT,
createDebris: function(gnomeProperties) {
_this.rezzedDebris = [];
var debrisFirstCollisionImpactDetector = function(entityA, entityB, collisionData) {
var debrisIndex = -1;
for (var i = 0; i < _this.rezzedDebris.length; i++) {
if (_this.rezzedDebris[i].part === entityA || _this.rezzedDebris[i].part === entityB) {
debrisIndex = i;
}
}
if (debrisIndex !== -1) {
var debris = _this.rezzedDebris[debrisIndex];
if (!debris.hadFirstCollision) {
debris.hadFirstCollision = true;
Entities.editEntity(debris.part, {
collisionSoundURL: '',
gravity: {x: 0.0, y: -10.0, z: 0.0}
});
debris.soundEmitter = _this.playSoundAtCurrentPosition(_this.cracklingSound,
Entities.getEntityProperties(debris.part, ['position']).position, 0.125, false, Math.random() * 2);
}
}
};
Entities.collisionWithEntity.connect(debrisFirstCollisionImpactDetector);
DEBRIS_PARTS.forEach(function(debrisPart) {
var debrisPosition = Vec3.sum(gnomeProperties.position, debrisPart.offset);
var debrisPart = Entities.addEntity({
type: 'Model',
name: 'weaponizedGnomeDebris',
modelURL: debrisPart.model,
collisionSoundURL: GNOME_CONTENT_PREFIX + '42900__freqman__glass-break-2-2.wav',
shapeType: 'sphere',
gravity: {
x: Math.random() - 0.5,
y: -10,
z: Math.random() - 0.5
},
damping: 0.35,
density: 10000,
restitution: 0.9,
dynamic: true,
position: debrisPosition,
lifetime: DEBRIS_LIFETIME,
userData: JSON.stringify({
hifiHomeKey: {
reset: true
}
})
}, AVATAR_ENTITIES);
var debrisParticle = Entities.addEntity({
name: 'ParticleSmoke',
type: 'ParticleEffect',
isEmitting: true,
parentID: debrisPart,
position: debrisPosition,
textures: SMOKE_TEXTURE_URL,
emitRate: 15,
emitSpeed: 0,
color: { red: 180, green: 180, blue: 180},
colorSpread: {
red: 0,
green: 0,
blue: 0
},
colorStart: {
red: 0,
green: 9,
blue: 1
},
colorFinish: {
red: 180,
green: 180,
blue: 180
},
lifespan: 1,
visible: true,
emitterShouldTrail: 1,
radiusSpread: 0,
radiusStart: 0.15,
radiusFinish: 0.00006,
lifetime: DEBRIS_LIFETIME,
polarStart: 0,
polarFinish: 0.23,
azimuthStart: -Math.PI,
azimuthFinish: Math.PI,
alpha: 0.1,
alphaSpread: .78,
alphaStart: 0.1,
alphaFinish: 0,
particleRadius: 0.063,
emitDimensions: {
x: 0,
y: 0.0,
z: 0
},
emitAcceleration: {
x: 0,
y: 0.8,
z: 0
},
maxParticles: 360,
speedSpread: 0.2,
emitOrientation: {
x: 0,
y: 0,
z: 0,
w: 0
},
accelerationSpread: {
x: 0,
y: 0,
z: 0
}
}, AVATAR_ENTITIES);
_this.rezzedDebris.push({part: debrisPart, particle: debrisParticle, soundEmitter: null, hadFirstCollision: false});
});
var updateSoundEmitter = Script.setInterval(function() {
_this.rezzedDebris.forEach(function(debris) {
if (debris.soundEmitter !== null) {
debris.soundEmitter.position = Entities.getEntityProperties(debris.part, ['position']).position;
}
});
} ,1/60);
},
// Used Philip's grenade script for reference on this
getImpactVelocityForPosition: function(epiCenter, impactPosition) {
var difference = Vec3.subtract(impactPosition, epiCenter);
var distance = Vec3.length(difference);
return Vec3.multiply(BLAST_POWER * 1.0 / distance, Vec3.normalize(difference));
},
getImpactAngularVelocityForVelocity: function(velocity) {
return Vec3.multiply(1.0 / Vec3.length(velocity), {
x: Math.random() * SPIN_RATE,
y: Math.random() * SPIN_RATE,
z: Math.random() * SPIN_RATE
});
},
playSoundAtCurrentPosition: function(sound, position, volume, loop, secondOffset) {
var audioProperties = {volume: 0.5};
if (loop !== undefined) {
audioProperties.loop = loop;
}
if (position !== undefined) {
audioProperties.position = position;
}
if (volume !== undefined) {
audioProperties.volume = volume;
}
if (loop !== undefined) {
audioProperties.loop = loop;
}
if (secondOffset !== undefined) {
audioProperties.secondOffset = secondOffset;
}
return Audio.playSound(sound, audioProperties);
},
timedExplode: function() {
if (_this.timerEnabled) {
return;
}
var time = Math.floor(Math.random() * 5000) + 1;
_this.detonationMode = DETONATION_MODE_TIMED;
Script.setTimeout(function() {
_this.explode();
}, time);
_this.timerEnabled = true;
},
explode: function() {
if (_this.exploding) {
return;
}
_this.exploding = true;
var properties = Entities.getEntityProperties(_this.entityID, ['position', 'rotation']);
if (_this.laughSoundInjector !== null && _this.laughSoundInjector.isPlaying()) {
_this.laughSoundInjector.stop();
}
_this.playSoundAtCurrentPosition(_this.explosionSound, properties.position);
Entities.addEntity({
color: {red: 255, green: 255, blue: 255},
isEmitting: 1,
maxParticles: 1000,
lifespan: 0.25,
emitRate: 1,
emitSpeed: 0.1,
speedSpread: 1,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: 0,
azimuthStart: 0,
azimuthFinish: 0,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: 0,
y: 0,
z: 0
},
particleRadius: BLAST_RADIUS,
radiusSpread: 0,
radiusStart: 0.361,
radiusFinish: 0.294,
colorSpread: {
red: 0,
green: 0,
blue: 0
},
colorStart: {
red: 255,
green: 255,
blue: 255
},
colorFinish: {
red: 255,
green: 255,
blue: 255
},
alpha: 1,
alphaSpread: 0,
alphaStart: -0.2,
alphaFinish: 0.5,
emitterShouldTrail: 0,
textures: EXPLOSION_TEXTURE_URL,
type: 'ParticleEffect',
lifetime: 1,
position: properties.position
});
Entities.editEntity(_this.entityID, {
visible: false,
dynamic: false,
collidesWith: '',
lifetime: DEBRIS_LIFETIME
});
_this.createDebris(properties);
Messages.sendMessage(_this.avatarImpactChannel, JSON.stringify({epiCenter: properties.position}));
Entities.findEntities(properties.position, BLAST_RADIUS).forEach(function(entity) {
var entityProperties = Entities.getEntityProperties(entity,
['dynamic', 'position', 'velocity', 'collisionless', 'collisionsWillMove', 'locked', 'collidesWith', 'type']);
if (entityProperties.dynamic && !entityProperties.collisionless && entityProperties.collisionsWillMove &&
!entityProperties.locked && entityProperties.collidesWith.indexOf('dynamic') !== -1 && entityProperties.type !== 'Zone')
{
var distance = Vec3.length(Vec3.subtract(entityProperties.position, properties.position));
if (distance < (BLAST_RADIUS / BLAST_FALLOFF)) {
Entities.callEntityMethod(entity, 'timedExplode');
}
var newVelocity = Vec3.sum(entityProperties.velocity, _this.getImpactVelocityForPosition(properties.position, entityProperties.position));
Entities.editEntity(entity, {
velocity: newVelocity,
angularVelocity: _this.getImpactAngularVelocityForVelocity(newVelocity)
});
}
});
},
preload: function(entityID) {
_this = this;
_this.entityID = entityID;
_this.avatarImpactChannel = 'GnomeExplosionImpact_' + entityID;
_this.explosionSound = SoundCache.getSound(EXPLOSION_SOUND_URL);
_this.laughSound = SoundCache.getSound(LAUGH_SOUND_URL);
_this.cracklingSound = SoundCache.getSound(FIRECRACKLING_SOUND_URL);
_this.smokeTexture = TextureCache.prefetch(SMOKE_TEXTURE_URL);
_this.explosionTexture = TextureCache.prefetch(EXPLOSION_TEXTURE_URL);
Messages.subscribe(_this.avatarImpactChannel);
Messages.messageReceived.connect(function(channel, message, senderID) {
if (channel === _this.avatarImpactChannel) {
var avatarImpactData = JSON.parse(message);
debugPrint("GOT IMPACT DATA: " + JSON.stringify(avatarImpactData, null, 4));
var velocity = _this.getImpactVelocityForPosition(avatarImpactData.epiCenter, MyAvatar.position);
if (Vec3.length(velocity) > 0) {
var thrust = Vec3.multiply(velocity, AVATAR_THRUST_MULITPLIER);
debugPrint("Thrust applied: " + JSON.stringify(thrust, null, 4));
MyAvatar.addThrust(thrust);
Controller.triggerHapticPulse(Vec3.length(Vec3.subtract(avatarImpactData.epiCenter, MyAvatar.position)) / BLAST_RADIUS, 2.0, BOTH_HANDS);
}
}
});
},
unload: function() {
if (_this.rezzedDebris !== null) {
_this.rezzedDebris.forEach(function(debris) {
if (debris.soundEmitter !== null) {
debris.soundEmitter.stop();
}
});
}
Messages.subscribe(_this.avatarImpactChannel);
},
startNearGrab: function(entityID, args) {
this.hand = args[0] == "left" ? 0 : 1;
debugPrint("grabbed with " + args[0] + " hand");
},
releaseGrab: function(entityID, args) {
var properties = Entities.getEntityProperties(_this.entityID, ['position', 'velocity']);
var throwSpeed = Vec3.length(properties.velocity);
if (_this.hand == null || throwSpeed < THROW_SPEED_TRESHOLD) {
return;
}
_this.thrown = true;
_this.laughSoundInjector = _this.playSoundAtCurrentPosition(_this.laughSound, properties.position);
var throwHand = this.hand === 'left' ? 0 : 1;
Controller.triggerShortHapticPulse(1, throwHand);
},
collisionWithEntity: function(entityA, entityB, collisionInfo) {
if (_this.thrown && _this.detonationMode === DETONATION_MODE_IMPACT) {
_this.explode();
return;
}
/* var otherID = _this.entityID === entityA ? entityB : entityA;
// Detect Ping pong balls
var otherProperties = Entities.getEntityProperties(otherID, ['shapeType', 'velocity']);
var otherVelocity = Vec3.length(otherProperties.velocity);
debugPrint('other velocity: ' + otherVelocity );
debugPrint(otherProperties.shapeType);
if (otherProperties.shapeType === 'sphere' && otherVelocity >= MIN_BULLET_IMPACT_SPEED) {
_this.explode();
return;
}*/
}
};
// entity scripts should return a newly constructed object of our type
return new WeaponizedGnome();
});