@ -1,43 +0,0 @@ |
|||
// Azulian (Red)
|
|||
// DEPRECATED: they both share azulian.js now.
|
|||
|
|||
function main() { |
|||
var playerSpeed = 4; |
|||
var gravity = 4; |
|||
var Vx = Vy = 0; |
|||
|
|||
var direction = "right"; |
|||
|
|||
Self.SetHitbox(0, 0, 32, 32) |
|||
Self.SetMobile(true); |
|||
Self.SetInventory(true); |
|||
Self.SetGravity(true); |
|||
Self.AddAnimation("walk-left", 100, ["red-wl1", "red-wl2", "red-wl3", "red-wl4"]); |
|||
Self.AddAnimation("walk-right", 100, ["red-wr1", "red-wr2", "red-wr3", "red-wr4"]); |
|||
|
|||
// Sample our X position every few frames and detect if we've hit a solid wall.
|
|||
var sampleTick = 0; |
|||
var sampleRate = 5; |
|||
var lastSampledX = 0; |
|||
|
|||
setInterval(function () { |
|||
if (sampleTick % sampleRate === 0) { |
|||
var curX = Self.Position().X; |
|||
var delta = Math.abs(curX - lastSampledX); |
|||
if (delta < 5) { |
|||
direction = direction === "right" ? "left" : "right"; |
|||
} |
|||
lastSampledX = curX; |
|||
} |
|||
sampleTick++; |
|||
|
|||
// TODO: Vector() requires floats, pain in the butt for JS,
|
|||
// the JS API should be friendlier and custom...
|
|||
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1)); |
|||
Self.SetVelocity(Vector(Vx, 0.0)); |
|||
|
|||
if (!Self.IsAnimating()) { |
|||
Self.PlayAnimation("walk-" + direction, null); |
|||
} |
|||
}, 100); |
|||
} |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1004 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1002 B |
After Width: | Height: | Size: 994 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 985 B |
After Width: | Height: | Size: 999 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 959 B After Width: | Height: | Size: 959 B |
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1022 B After Width: | Height: | Size: 1022 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -1,38 +1,62 @@ |
|||
function main() { |
|||
var playerSpeed = 12; |
|||
var gravity = 4; |
|||
var Vx = Vy = 0; |
|||
const playerSpeed = 12; |
|||
|
|||
var animating = false; |
|||
var animStart = animEnd = 0; |
|||
var animFrame = animStart; |
|||
let Vx = Vy = 0, |
|||
walking = false, |
|||
direction = "right", |
|||
lastDirection = direction; |
|||
|
|||
function main() { |
|||
Self.SetMobile(true); |
|||
Self.SetInventory(true); |
|||
Self.SetGravity(true); |
|||
Self.SetHitbox(0, 0, 32, 52); |
|||
Self.AddAnimation("walk-left", 200, ["stand-left", "walk-left-1", "walk-left-2", "walk-left-3", "walk-left-2", "walk-left-1"]); |
|||
Self.AddAnimation("walk-right", 200, ["stand-right", "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]); |
|||
Self.AddAnimation("idle-left", 200, ["idle-left-1", "idle-left-2", "idle-left-3", "idle-left-2"]); |
|||
Self.AddAnimation("idle-right", 200, ["idle-right-1", "idle-right-2", "idle-right-3", "idle-right-2"]); |
|||
|
|||
// If the player suddenly changes direction, reset the animation state to quickly switch over.
|
|||
let lastVelocity = Vector(0, 0); |
|||
|
|||
Events.OnKeypress(function (ev) { |
|||
Events.OnKeypress((ev) => { |
|||
Vx = 0; |
|||
Vy = 0; |
|||
|
|||
let curVelocity = Self.GetVelocity(); |
|||
if ((lastVelocity.X < 0 && curVelocity.X > 0) || |
|||
(lastVelocity.X > 0 && curVelocity.X < 0)) { |
|||
Self.StopAnimation(); |
|||
} |
|||
lastVelocity = curVelocity; |
|||
lastDirection = direction; |
|||
|
|||
let wasWalking = walking; |
|||
if (ev.Right) { |
|||
if (!Self.IsAnimating()) { |
|||
Self.PlayAnimation("walk-right", null); |
|||
} |
|||
direction = "right"; |
|||
Vx = playerSpeed; |
|||
walking = true; |
|||
} else if (ev.Left) { |
|||
if (!Self.IsAnimating()) { |
|||
Self.PlayAnimation("walk-left", null); |
|||
} |
|||
direction = "left"; |
|||
Vx = -playerSpeed; |
|||
walking = true; |
|||
} else { |
|||
// Has stopped walking!
|
|||
walking = false; |
|||
stoppedWalking = true; |
|||
} |
|||
|
|||
// Should we stop animating? (changed state)
|
|||
if (direction !== lastDirection || wasWalking !== walking) { |
|||
Self.StopAnimation(); |
|||
animating = false; |
|||
} |
|||
|
|||
// Self.SetVelocity(Point(Vx, Vy));
|
|||
// And play what animation?
|
|||
if (!Self.IsAnimating()) { |
|||
if (walking) { |
|||
Self.PlayAnimation("walk-"+direction, null); |
|||
} else { |
|||
Self.PlayAnimation("idle-"+direction, null); |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,14 @@ |
|||
ALL: build |
|||
|
|||
.PHONY: build |
|||
build: |
|||
doodad convert -t "Crusher" sleep.png peek-left.png peek-right.png \
|
|||
angry.png ouch.png crusher.doodad |
|||
doodad install-script crusher.js crusher.doodad |
|||
|
|||
# Tag the category for these doodads |
|||
for i in *.doodad; do\
|
|||
doodad edit-doodad --tag "category=creatures" $${i};\
|
|||
done |
|||
|
|||
cp *.doodad ../../../assets/doodads/ |
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,207 @@ |
|||
// Crusher
|
|||
|
|||
/* |
|||
A.I. Behaviors: |
|||
|
|||
- Sleeps and hangs in the air in a high place. |
|||
- When a player gets nearby, it begins "peeking" in their direction. |
|||
- When the player is below, tries to drop and crush them or any |
|||
other mobile doodad. |
|||
- The top edge is safe to walk on and ride back up like an elevator. |
|||
*/ |
|||
|
|||
let direction = "left", |
|||
dropSpeed = 12, |
|||
riseSpeed = 4, |
|||
watchRadius = 300, // player nearby distance to start peeking
|
|||
fallRadius = 120, // player distance before it drops
|
|||
helmetThickness = 48, // safe solid hitbox height
|
|||
fireThickness = 12, // dangerous bottom thickness
|
|||
targetAltitude = Self.Position() |
|||
lastAltitude = targetAltitude.Y |
|||
size = Self.Size(); |
|||
|
|||
const states = { |
|||
idle: 0, |
|||
peeking: 1, |
|||
drop: 2, |
|||
falling: 3, |
|||
hit: 4, |
|||
rising: 5, |
|||
}; |
|||
let state = states.idle; |
|||
|
|||
function main() { |
|||
Self.SetMobile(true); |
|||
Self.SetGravity(false); |
|||
Self.SetInvulnerable(true); |
|||
Self.SetHitbox(5, 2, 90, 73); |
|||
Self.AddAnimation("hit", 50, |
|||
["angry", "ouch", "angry", "angry", "angry", "angry", |
|||
"sleep", "sleep", "sleep", "sleep", "sleep", "sleep", |
|||
"sleep", "sleep", "sleep", "sleep", "sleep", "sleep", |
|||
"sleep", "sleep", "sleep", "sleep", "sleep", "sleep"], |
|||
) |
|||
|
|||
// Player Character controls?
|
|||
if (Self.IsPlayer()) { |
|||
return player(); |
|||
} |
|||
|
|||
let hitbox = Self.Hitbox(); |
|||
|
|||
Events.OnCollide((e) => { |
|||
// The bottom is deadly if falling.
|
|||
if (state === states.falling || state === states.hit && e.Settled) { |
|||
if (e.Actor.IsMobile() && e.InHitbox && !e.Actor.Invulnerable()) { |
|||
if (e.Overlap.H > 72) { |
|||
if (e.Actor.IsPlayer()) { |
|||
FailLevel("Don't get crushed!"); |
|||
return; |
|||
} else { |
|||
e.Actor.Destroy(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Our top edge is always solid.
|
|||
if (e.Actor.IsPlayer() && e.InHitbox) { |
|||
if (e.Overlap.Y < helmetThickness) { |
|||
// Be sure to position them snug on top.
|
|||
// TODO: this might be a nice general solution in the
|
|||
// collision detector...
|
|||
e.Actor.MoveTo(Point( |
|||
e.Actor.Position().X, |
|||
Self.Position().Y - e.Actor.Hitbox().Y - e.Actor.Hitbox().H, |
|||
)) |
|||
e.Actor.SetGrounded(true); |
|||
} |
|||
} |
|||
|
|||
// The whole hitbox is ordinarily solid.
|
|||
if (state !== state.falling) { |
|||
if (e.Actor.IsMobile() && e.InHitbox) { |
|||
return false; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
setInterval(() => { |
|||
// Find the player.
|
|||
let player = Actors.FindPlayer(), |
|||
playerPoint = player.Position(), |
|||
point = Self.Position(), |
|||
delta = 0, |
|||
nearby = false, |
|||
below = false; |
|||
|
|||
// Face the player.
|
|||
if (playerPoint.X < point.X + (size.W / 2)) { |
|||
direction = "left"; |
|||
delta = Math.abs(playerPoint.X - (point.X + (size.W/2))); |
|||
} |
|||
else if (playerPoint.X > point.X + (size.W / 2)) { |
|||
direction = "right"; |
|||
delta = Math.abs(playerPoint.X - (point.X + (size.W/2))); |
|||
} |
|||
|
|||
if (delta < watchRadius) { |
|||
nearby = true; |
|||
} |
|||
if (delta < fallRadius) { |
|||
// Check if the player is below us.
|
|||
if (playerPoint.Y > point.Y + size.H) { |
|||
below = true; |
|||
} |
|||
} |
|||
|
|||
switch (state) { |
|||
case states.idle: |
|||
if (nearby) { |
|||
Self.ShowLayerNamed("peek-"+direction); |
|||
} else { |
|||
Self.ShowLayerNamed("sleep"); |
|||
} |
|||
|
|||
if (below) { |
|||
state = states.drop; |
|||
} else if (nearby) { |
|||
state = states.peeking; |
|||
} |
|||
|
|||
break; |
|||
case states.peeking: |
|||
if (nearby) { |
|||
Self.ShowLayerNamed("peek-"+direction); |
|||
} else { |
|||
state = states.idle; |
|||
break; |
|||
} |
|||
|
|||
if (below) { |
|||
state = states.drop; |
|||
} |
|||
|
|||
break; |
|||
case states.drop: |
|||
// Begin the fall.
|
|||
Self.ShowLayerNamed("angry"); |
|||
Self.SetVelocity(Vector(0.0, dropSpeed)); |
|||
lastAltitude = -point.Y; |
|||
state = states.falling; |
|||
case states.falling: |
|||
Self.ShowLayerNamed("angry"); |
|||
Self.SetVelocity(Vector(0.0, dropSpeed)); |
|||
|
|||
// Landed?
|
|||
if (point.Y === lastAltitude) { |
|||
Sound.Play("crumbly-break.wav") |
|||
state = states.hit; |
|||
Self.PlayAnimation("hit", () => { |
|||
state = states.rising; |
|||
}); |
|||
} |
|||
break; |
|||
case states.hit: |
|||
// A transitory state while the hit animation
|
|||
// plays out.
|
|||
break; |
|||
case states.rising: |
|||
Self.ShowLayerNamed("sleep"); |
|||
Self.SetVelocity(Vector(0, -riseSpeed)); |
|||
|
|||
point = Self.Position(); |
|||
if (point.Y <= targetAltitude.Y+4 || point.Y === lastAltitude.Y) { |
|||
Self.MoveTo(targetAltitude); |
|||
Self.SetVelocity(Vector(0, 0)) |
|||
state = states.idle; |
|||
} |
|||
} |
|||
|
|||
lastAltitude = point.Y; |
|||
}, 100); |
|||
} |
|||
|
|||
// If under control of the player character.
|
|||
function player() { |
|||
Events.OnKeypress((ev) => { |
|||
if (ev.Right) { |
|||
direction = "right"; |
|||
} else if (ev.Left) { |
|||
direction = "left"; |
|||
} |
|||
|
|||
// Jump!
|
|||
if (ev.Down) { |
|||
Self.ShowLayerNamed("angry"); |
|||
return; |
|||
} else if (ev.Right && ev.Left) { |
|||
Self.ShowLayerNamed("ouch"); |
|||
} else if (ev.Right || ev.Left) { |
|||
Self.ShowLayerNamed("peek-"+direction); |
|||
} else { |
|||
Self.ShowLayerNamed("sleep"); |
|||
} |
|||
}); |
|||
} |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
@ -1,27 +0,0 @@ |
|||
// DEPRECATED: old locked door script. Superceded by colored-door.js.
|
|||
function main() { |
|||
Self.AddAnimation("open", 0, [1]); |
|||
var unlocked = false; |
|||
var color = Self.GetTag("color"); |
|||
|
|||
Self.SetHitbox(16, 0, 32, 64); |
|||
|
|||
Events.OnCollide(function(e) { |
|||
if (unlocked) { |
|||
return; |
|||
} |
|||
|
|||
if (e.InHitbox) { |
|||
var data = e.Actor.GetData("key:" + color); |
|||
if (data === "") { |
|||
// Door is locked.
|
|||
return false; |
|||
} |
|||
|
|||
if (e.Settled) { |
|||
unlocked = true; |
|||
Self.PlayAnimation("open", null); |
|||
} |
|||
} |
|||
}); |
|||
} |
@ -0,0 +1,58 @@ |
|||
ALL: build |
|||
|
|||
.PHONY: build |
|||
build: |
|||
### |
|||
# Gemstones |
|||
### |
|||
|
|||
doodad convert -t "Gemstone (Green)" green-1.png green-2.png green-3.png green-4.png \
|
|||
gem-green.doodad |
|||
doodad install-script gemstone.js gem-green.doodad |
|||
doodad edit-doodad --tag "color=green" gem-green.doodad |
|||
|
|||
doodad convert -t "Gemstone (Red)" red-1.png red-2.png red-3.png red-4.png \
|
|||
gem-red.doodad |
|||
doodad install-script gemstone.js gem-red.doodad |
|||
doodad edit-doodad --tag "color=red" gem-red.doodad |
|||
|
|||
doodad convert -t "Gemstone (Blue)" blue-1.png blue-2.png blue-3.png blue-4.png \
|
|||
gem-blue.doodad |
|||
doodad install-script gemstone.js gem-blue.doodad |
|||
doodad edit-doodad --tag "color=blue" gem-blue.doodad |
|||
|
|||
doodad convert -t "Gemstone (Yellow)" yellow-1.png yellow-2.png yellow-3.png yellow-4.png \
|
|||
gem-yellow.doodad |
|||
doodad install-script gemstone.js gem-yellow.doodad |
|||
doodad edit-doodad --tag "color=yellow" gem-yellow.doodad |
|||
|
|||
### |
|||
# Totems |
|||
### |
|||
|
|||
doodad convert -t "Gemstone Totem (Green)" totem-green-1.png totem-green-2.png totem-green-3.png \
|
|||
totem-green-4.png totem-green-0.png gem-totem-green.doodad |
|||
doodad install-script totem.js gem-totem-green.doodad |
|||
doodad edit-doodad --tag "color=green" gem-totem-green.doodad |
|||
|
|||
doodad convert -t "Gemstone Totem (Yellow)" totem-yellow-1.png totem-yellow-2.png totem-yellow-3.png \
|
|||
totem-yellow-4.png totem-yellow-0.png gem-totem-yellow.doodad |
|||
doodad install-script totem.js gem-totem-yellow.doodad |
|||
doodad edit-doodad --tag "color=yellow" gem-totem-yellow.doodad |
|||
|
|||
doodad convert -t "Gemstone Totem (Blue)" totem-blue-1.png totem-blue-2.png totem-blue-3.png \
|
|||
totem-blue-4.png totem-blue-0.png gem-totem-blue.doodad |
|||
doodad install-script totem.js gem-totem-blue.doodad |
|||
doodad edit-doodad --tag "color=blue" gem-totem-blue.doodad |
|||
|
|||
doodad convert -t "Gemstone Totem (Red)" totem-red-1.png totem-red-2.png totem-red-3.png \
|
|||
totem-red-4.png totem-red-0.png gem-totem-red.doodad |
|||
doodad install-script totem.js gem-totem-red.doodad |
|||
doodad edit-doodad --tag "color=red" gem-totem-red.doodad |
|||
|
|||
# Tag the category for these doodads |
|||
for i in *.doodad; do\
|
|||
doodad edit-doodad --tag "category=doors" $${i};\
|
|||
done |
|||
|
|||
cp *.doodad ../../../assets/doodads/ |
After Width: | Height: | Size: 891 B |
After Width: | Height: | Size: 927 B |
After Width: | Height: | Size: 903 B |
After Width: | Height: | Size: 835 B |
@ -0,0 +1,24 @@ |
|||
// Gem stone collectibles/keys.
|
|||
|
|||
const color = Self.GetTag("color"), |
|||
shimmerFreq = 1000; |
|||
|
|||
function main() { |
|||
Self.SetMobile(true); |
|||
Self.SetGravity(true); |
|||
|
|||
Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]); |
|||
Events.OnCollide((e) => { |
|||
if (e.Settled) { |
|||
if (e.Actor.HasInventory()) { |
|||
Sound.Play("item-get.wav") |
|||
e.Actor.AddItem(Self.Filename, 1); |
|||
Self.Destroy(); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
setInterval(() => { |
|||
Self.PlayAnimation("shimmer", null); |
|||
}, shimmerFreq); |
|||
} |
After Width: | Height: | Size: 785 B |
After Width: | Height: | Size: 807 B |
After Width: | Height: | Size: 864 B |
After Width: | Height: | Size: 835 B |
After Width: | Height: | Size: 711 B |
After Width: | Height: | Size: 702 B |
After Width: | Height: | Size: 712 B |
After Width: | Height: | Size: 724 B |
After Width: | Height: | Size: 798 B |
After Width: | Height: | Size: 958 B |
After Width: | Height: | Size: 1005 B |
After Width: | Height: | Size: 987 B |
After Width: | Height: | Size: 921 B |
After Width: | Height: | Size: 711 B |
After Width: | Height: | Size: 887 B |
After Width: | Height: | Size: 938 B |
After Width: | Height: | Size: 978 B |
After Width: | Height: | Size: 956 B |
After Width: | Height: | Size: 752 B |
After Width: | Height: | Size: 811 B |
After Width: | Height: | Size: 805 B |
After Width: | Height: | Size: 790 B |
After Width: | Height: | Size: 817 B |
After Width: | Height: | Size: 837 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,100 @@ |
|||
// Gem stone totem socket.
|
|||
|
|||
/* |
|||
The Totem is a type of key-door that holds onto its corresponding |
|||
Gemstone. When a doodad holding the right Gemstone touches the |
|||
totem, the totem takes the gemstone and activates. |
|||
|
|||
If the Totem is not linked to any other Totems, it immediately |
|||
sends a power(true) signal upon activation. |
|||
|
|||
If the Totem is linked to other Totems, it waits until all totems |
|||
have been activated before it will emit a power signal. Only one |
|||
such totem needs to be linked to e.g. an Electric Door - no matter |
|||
which totem is solved last, they'll all emit a power signal when |
|||
all of their linked totems are activated. |
|||
*/ |
|||
|
|||
let color = Self.GetTag("color"), |
|||
keyname = "gem-"+color+".doodad", |
|||
activated = false, |
|||
linkedReceiver = false, // is linked to a non-totem which might want power
|
|||
totems = {}, // linked totems
|
|||
shimmerFreq = 1000; |
|||
|
|||
function main() { |
|||
// Show the hollow socket on level load (last layer)
|
|||
Self.ShowLayer(4); |
|||
|
|||
// Find any linked totems.
|
|||
for (let link of Self.GetLinks()) { |
|||
if (link.Filename.indexOf("gem-totem") > -1) { |
|||
totems[link.ID()] = false; |
|||
} else { |
|||
linkedReceiver = true; |
|||
} |
|||
} |
|||
|
|||
// Shimmer animation is just like the gemstones: first 4 frames
|
|||
// are the filled socket sprites.
|
|||
Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]); |
|||
|
|||
Events.OnCollide((e) => { |
|||
if (activated) return; |
|||
|
|||
if (e.Actor.IsMobile() && e.Settled) { |
|||
// Do they have our gemstone?
|
|||
let hasKey = e.Actor.HasItem(keyname) >= 0; |
|||
if (!hasKey) { |
|||
return; |
|||
} |
|||
|
|||
// Take the gemstone.
|
|||
e.Actor.RemoveItem(keyname, 1); |
|||
Self.ShowLayer(0); |
|||
|
|||
// Emit to our linked totem neighbors.
|
|||
activated = true; |
|||
Message.Publish("gem-totem:activated", Self.ID()); |
|||
tryPower(); |
|||
} |
|||
}); |
|||
|
|||
Message.Subscribe("gem-totem:activated", (totemId) => { |
|||
totems[totemId] = true; |
|||
tryPower(); |
|||
}) |
|||
|
|||
setInterval(() => { |
|||
if (activated) { |
|||
Self.PlayAnimation("shimmer", null); |
|||
} |
|||
}, shimmerFreq); |
|||
} |
|||
|
|||
// Try to send a power signal for an activated totem.
|
|||
function tryPower() { |
|||
// Only emit power if we are linked to something other than a totem.
|
|||
if (!linkedReceiver) { |
|||
return; |
|||
} |
|||
|
|||
// Can't if any of our linked totems aren't activated.
|
|||
try { |
|||
for (let totemId of Object.keys(totems)) { |
|||
if (totems[totemId] === false) { |
|||
return; |
|||
} |
|||
} |
|||
} catch(e) { |
|||
console.error("Caught: %s", e); |
|||
} |
|||
|
|||
// Can't if we aren't powered.
|
|||
if (activated === false) { |
|||
return; |
|||
} |
|||
|
|||
// Emit power!
|
|||
Message.Publish("power", true); |
|||
} |
After Width: | Height: | Size: 971 B |
After Width: | Height: | Size: 921 B |
After Width: | Height: | Size: 898 B |
After Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 956 B |