Add ta4 movecontroller

This commit is contained in:
Joachim Stolberg 2021-10-04 20:39:36 +02:00
parent b643482c02
commit 546e9e8349
6 changed files with 773 additions and 2 deletions

View File

@ -275,6 +275,7 @@ dofile(MP.."/logic/doorcontroller.lua") -- old
dofile(MP.."/logic/doorcontroller2.lua") -- new
dofile(MP.."/logic/collector.lua")
dofile(MP.."/logic/button_4x.lua")
dofile(MP.."/logic/movecontroller.lua")
if minetest.global_exists("mesecon") then
dofile(MP.."/logic/mesecons_converter.lua")
end

View File

@ -19,6 +19,16 @@ local NDEF = function(pos) return (minetest.registered_nodes[techage.get_node_lv
local logic = techage.logic
local WRENCH_MENU = {
{
type = "ascii",
name = "command",
label = S("Command"),
tooltip = S("Command to be sent"),
default = "on",
},
}
local function switch_on(pos)
local cycle_time = M(pos):get_int("cycle_time")
local name = techage.get_node_lvm(pos).name
@ -27,7 +37,9 @@ local function switch_on(pos)
elseif name == "techage:ta4_button_off" then
logic.swap_node(pos, "techage:ta4_button_on")
end
logic.send_on(pos, M(pos), cycle_time)
local meta = M(pos)
local cmnd = meta:contains("command") and meta:get_string("command") or "on"
logic.send_cmnd(pos, M(pos), cmnd, cycle_time)
minetest.sound_play("techage_button", {
pos = pos,
gain = 0.5,
@ -249,6 +261,7 @@ minetest.register_node("techage:ta4_button_off", {
meta:set_int("cycle_time", 0)
end,
ta4_formspec = WRENCH_MENU,
on_receive_fields = on_receive_fields,
on_rightclick = on_rightclick_on,
techage_set_numbers = techage_set_numbers,

View File

@ -230,7 +230,7 @@ minetest.register_node("techage:ta3_doorcontroller2", {
local meta = M(pos)
local inv = meta:get_inventory()
inv:set_size('main', 16)
logic.after_place_node(pos, placer, "techage:ta3_doorcontroller", S("TA3 Door Controller II"))
logic.after_place_node(pos, placer, "techage:ta3_doorcontroller2", S("TA3 Door Controller II"))
logic.infotext(meta, S("TA3 Door Controller II"))
local nvm = techage.get_nvm(pos)
meta:set_string("formspec", formspec1(nvm, meta))

View File

@ -67,6 +67,15 @@ function techage.logic.send_on(pos, meta, time)
return own_num == numbers
end
function techage.logic.send_cmnd(pos, meta, cmnd, time)
local own_num = meta:get_string("node_number") or ""
local numbers = meta:get_string("numbers") or ""
if time and time > 0 then
minetest.get_node_timer(pos):start(time)
end
techage.send_multi(own_num, numbers, cmnd)
end
function techage.logic.send_off(pos, meta)
local own_num = meta:get_string("node_number") or ""
local numbers = meta:get_string("numbers") or ""

748
logic/movecontroller.lua Normal file
View File

@ -0,0 +1,748 @@
--[[
TechAge
=======
Copyright (C) 2020-2021 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
TA4 Move Controller
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = techage.S
-------------------------------------------------------------------------------
-- Entity / Move / Attach / Detach
-------------------------------------------------------------------------------
local MIN_SPEED = 0.4
local MAX_SPEED = 10
local function to_vector(s)
local x,y,z = unpack(string.split(s, ","))
if x and y and z then
return {
x = tonumber(x) or 0,
y = tonumber(y) or 0,
z = tonumber(z) or 0,
}
end
return {x=0, y=0, z=0}
end
-- Only the ID ist stored, not the object
local function get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
-- determine exact position of attached entities
local function obj_pos(obj)
local _, _, pos = obj:get_attach()
pos = vector.divide(pos, 29)
return vector.add(obj:get_pos(), pos)
end
-- Check access conflicts with other mods
local function lock_player(player)
local meta = player:get_meta()
if meta:get_int("player_physics_locked") == 0 then
meta:set_int("player_physics_locked", 1)
meta:set_string("player_physics_locked_by", "ta_movecontroller")
return true
end
return false
end
local function unlock_player(player)
local meta = player:get_meta()
if meta:get_int("player_physics_locked") == 1 then
if meta:get_string("player_physics_locked_by") == "ta_movecontroller" then
meta:set_int("player_physics_locked", 0)
meta:set_string("player_physics_locked_by", "")
return true
end
end
return false
end
local function detach_player(player)
local pos = obj_pos(player)
player:set_detach()
player:set_properties({visual_size = {x = 1, y = 1}})
player:set_pos(pos)
-- TODO: move to save position
end
-- Attach player/mob to given parent object (block)
local function attach_single_object(parent, obj, dir)
local self = parent:get_luaentity()
local rot = obj:get_rotation() or {x=0, y=0, z=0}
local prop = obj:get_properties()
local res = obj:get_attach()
if not res and prop and prop.collisionbox then
dir = table.copy(dir)
dir.y = dir.y - 0.5 - prop.collisionbox[2]
dir = vector.multiply(dir, 29)
obj:set_attach(parent, "", dir, rot, true)
obj:set_properties({visual_size = {x = 2.9, y = 2.9}})
if obj:is_player() then
if lock_player(obj) then
table.insert(self.players, obj:get_player_name())
end
else
table.insert(self.entities, get_object_id(obj))
end
end
end
-- Attach all entities around to the parent object (block).
local function attach_objects(pos, parent, dir)
local pos1 = vector.add(pos, dir)
local self = parent:get_luaentity()
self.players = self.players or {}
self.entities = self.entities or {}
for _, obj in pairs(minetest.get_objects_inside_radius(pos1, 0.8)) do
local entity = obj:get_luaentity()
if entity then
if entity.name == "__builtin:item" then -- dropped items
--obj:set_attach(objref, "", {x=0, y=0, z=0}, {x=0, y=0, z=0}, true) -- hier kracht es
elseif entity.name ~= "techage:move_item" then
attach_single_object(parent, obj, dir)
end
elseif obj:is_player() then
attach_single_object(parent, obj, dir)
end
end
end
-- Detach all attached entities from the parent object (block)
local function detach_objects(pos, parent, dir)
local self = parent:get_luaentity()
for _, objID in ipairs(self.entities or {}) do
local entity = minetest.luaentities[objID]
if entity then
local obj = entity.object
local pos1 = obj_pos(obj)
obj:set_detach()
obj:set_properties({visual_size = {x = 1, y = 1}})
obj:set_pos(pos1)
end
end
for _, name in ipairs(self.players or {}) do
local obj = minetest.get_player_by_name(name)
if obj then
local pos1 = obj_pos(obj)
obj:set_detach()
obj:set_properties({visual_size = {x = 1, y = 1}})
obj:set_pos(pos1)
unlock_player(obj)
end
end
self.entities = nil
self.players = nil
end
local function entity_to_node(pos, obj)
local name = obj:get_luaentity().item or "air"
local rot = obj:get_rotation()
detach_objects(pos, obj)
obj:remove()
pos = vector.round(pos)
local dir = minetest.yaw_to_dir(rot.y or 0)
local param2 = minetest.dir_to_facedir(dir) or 0
local node = minetest.get_node(pos)
local ndef1 = minetest.registered_nodes[name]
local ndef2 = minetest.registered_nodes[node.name]
if ndef1 and ndef2 and ndef2.buildable_to then
minetest.set_node(pos, {name = name, param2 = param2})
elseif ndef1 then
minetest.add_item(pos, ItemStack(name))
end
end
local function node_to_entity(pos, handover)
local node = minetest.get_node(pos)
minetest.remove_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
local yaw = minetest.dir_to_yaw(dir)
local obj = minetest.add_entity(pos, "techage:move_item")
local self = obj:get_luaentity()
obj:set_rotation({x=0, y=yaw, z=0})
obj:set_properties({wield_item = node.name})
obj:set_armor_groups({immortal=1})
self.item = node.name
self.handover = handover
return obj
end
local function capture_entity(pos)
local l = minetest.get_objects_in_area(pos, pos)
for _, obj in ipairs(l) do
local self = obj:get_luaentity()
if self and self.name == "techage:move_item" then
return obj
end
end
end
-- move block direction
local function determine_dir(pos1, pos2)
local vdist = vector.subtract(pos2, pos1)
local ndist = vector.length(vdist)
return vector.divide(vdist, ndist)
end
local function move_entity(obj, pos2, dir, max_speed)
local self = obj:get_luaentity()
self.max_speed = max_speed
self.dest_pos = table.copy(pos2)
self.dir = dir
local acc = vector.multiply(dir, max_speed / 2)
obj:set_acceleration(acc)
end
-- Handover the entity to the next movecontroller
local function handover_to(self)
local info = techage.get_node_info(self.handover)
if info and info.name == "techage:ta4_movecontroller" then
local mem = techage.get_mem(info.pos)
if not mem.entities_are_there then
mem.entities_are_there = true
minetest.after(0.2, techage.send_single, "0", self.handover, "handover")
end
return true
end
end
minetest.register_entity("techage:move_item", {
initial_properties = {
pointable = true,
makes_footstep_sound = true,
static_save = true,
collide_with_objects = false,
physical = false,
visual = "wielditem",
wield_item = "default:dirt",
visual_size = {x=0.67, y=0.67, z=0.67},
selectionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
},
get_staticdata = function(self)
return minetest.serialize({
item = self.item,
max_speed = self.max_speed,
dest_pos = self.dest_pos,
dir = self.dir,
})
end,
on_activate = function(self, staticdata)
if staticdata then
local tbl = minetest.deserialize(staticdata) or {}
self.item = tbl.item or "air"
self.max_speed = tbl.max_speed or MAX_SPEED
self.dest_pos = tbl.dest_pos or self.object:get_pos()
self.dir = tbl.dir or {x=0, y=0, z=0}
self.object:set_properties({wield_item = self.item})
end
end,
on_step = function(self, dtime, moveresult)
if self.dest_pos then
local obj = self.object
local pos = obj:get_pos()
local dist = vector.distance(pos, self.dest_pos)
local speed = vector.length(obj:get_velocity())
-- Landing
if dist < 0.05 then
obj:move_to(self.dest_pos, true)
obj:set_acceleration({x=0, y=0, z=0})
obj:set_velocity({x=0, y=0, z=0})
self.dest_pos = nil
if not self.handover or not handover_to(self) then
minetest.after(0.5, entity_to_node, pos, obj)
end
self.ttl = 2
return
end
-- Braking or limit max speed
if speed > (dist * 2) or speed > self.max_speed then
local speed = math.min(speed, math.max(dist * 2, MIN_SPEED))
local vel = vector.multiply(self.dir,speed)
obj:set_velocity(vel)
obj:set_acceleration({x=0, y=0, z=0})
end
elseif self.ttl then
self.ttl = self.ttl - dtime
if self.ttl < 0 then
local obj = self.object
local pos = obj:get_pos()
entity_to_node(pos, obj)
end
end
end,
})
-------------------------------------------------------------------------------
-- Marker / Record
-------------------------------------------------------------------------------
local MarkedNodes = {} -- t[player] = {{entity, pos},...}
local CurrentPos -- to mark punched entities
local RegisteredNodes = {} -- to be checked before removed/placed
local function is_air_like(pos)
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.buildable_to then
return true
end
return false
end
local function is_simple_node(pos)
-- special handling
local name = minetest.get_node(pos).name
if RegisteredNodes[name] ~= nil then
return RegisteredNodes[name]
end
local ndef = minetest.registered_nodes[name]
if not ndef or name == "air" or name == "ignore" then return false end
-- don't remove nodes with some intelligence or undiggable nodes
if ndef.drop == "" then return false end
if ndef.diggable == false then return false end
if ndef.after_dig_node then return false end
return true
end
local function table_add(tbl, offs)
if not tbl or not offs then return end
local tbl2 = {}
for _, v in ipairs(tbl) do
tbl2[#tbl2 + 1] = vector.add(v, offs)
end
return tbl2
end
local function unmark_position(name, pos)
pos = vector.round(pos)
for idx,item in ipairs(MarkedNodes[name] or {}) do
if vector.equals(pos, item.pos) then
item.entity:remove()
table.remove(MarkedNodes[name], idx)
CurrentPos = pos
return
end
end
end
local function unmark_all(name)
for _,item in ipairs(MarkedNodes[name] or {}) do
item.entity:remove()
end
MarkedNodes[name] = nil
end
local function mark_position(name, pos)
MarkedNodes[name] = MarkedNodes[name] or {}
pos = vector.round(pos)
if not CurrentPos or not vector.equals(pos, CurrentPos) then -- entity not punched?
local entity = minetest.add_entity(pos, "techage:moveblock_marker")
if entity ~= nil then
entity:get_luaentity().player_name = name
table.insert(MarkedNodes[name], {pos = pos, entity = entity})
end
CurrentPos = nil
return true
end
CurrentPos = nil
end
local function get_poslist(name)
local lst = {}
for _,item in ipairs(MarkedNodes[name] or {}) do
table.insert(lst, item.pos)
end
return lst
end
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
if puncher and puncher:is_player() then
local name = puncher:get_player_name()
if not MarkedNodes[name] then
return
end
mark_position(name, pointed_thing.under)
end
end)
minetest.register_entity(":techage:moveblock_marker", {
initial_properties = {
visual = "cube",
textures = {
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
"techage_cube_mark.png",
},
--use_texture_alpha = true,
physical = false,
visual_size = {x = 1.1, y = 1.1},
collisionbox = {-0.55,-0.55,-0.55, 0.55,0.55,0.55},
glow = 8,
},
on_step = function(self, dtime)
self.ttl = (self.ttl or 2400) - 1
if self.ttl <= 0 then
local pos = self.object:get_pos()
unmark_position(self.player_name, pos)
end
end,
on_punch = function(self, hitter)
local pos = self.object:get_pos()
local name = hitter:get_player_name()
if name == self.player_name then
unmark_position(name, pos)
end
end,
})
-------------------------------------------------------------------------------
-- TA4 Move Controller
-------------------------------------------------------------------------------
local WRENCH_MENU = {
{
type = "dropdown",
choices = "0.5,1,2,4,6,8,10",
name = "max_speed",
label = S("Maximum Speed"),
tooltip = S("Maximum speed for the moving block."),
default = "10",
},
{
type = "number",
name = "handoverB",
label = S("Handover to B"),
tooltip = S("Number of the next movecontroller."),
default = "",
},
{
type = "number",
name = "handoverA",
label = S("Handover to A"),
tooltip = S("Number of the previous movecontroller."),
default = "",
},
}
local function formspec(nvm, meta)
local status = meta:get_string("status")
local distance = meta:contains("distance") and meta:get_string("distance") or "0,3,0"
return "size[8,5]" ..
default.gui_bg ..
default.gui_bg_img ..
default.gui_slots ..
"box[0,-0.1;7.2,0.5;#c6e8ff]" ..
"label[0.2,-0.1;" .. minetest.colorize( "#000000", S("TA4 Move Controller")) .. "]" ..
techage.wrench_image(7.4, -0.05) ..
"button[0.1,0.8;3.8,1;record;" .. S("Record") .. "]" ..
"button[4.1,0.8;3.8,1;ready;" .. S("Done") .. "]" ..
"field[0.4,2.5;3.8,1;distance;" .. S("Move distance (A to B)") .. ";" .. distance .. "]" ..
"button[4.1,2.2;3.8,1;store;" .. S("Store") .. "]" ..
"button[0.1,3.3;3.8,1;moveAB;" .. S("Move A-B") .. "]" ..
"button[4.1,3.3;3.8,1;moveBA;" .. S("Move B-A") .. "]" ..
"label[0.3,4.3;" .. status .. "]"
end
local function move_node(pos, pos1, pos2, max_speed, handover)
local meta = M(pos)
local dir = determine_dir(pos1, pos2)
local obj = node_to_entity(pos1, handover)
attach_objects(pos1, obj, {x=0, y=1, z=0})
if dir.y == 0 then
if (dir.x ~= 0 and dir.z == 0) or (dir.x == 0 and dir.z ~= 0) then
attach_objects(pos1, obj, dir)
end
end
move_entity(obj, pos2, dir, max_speed)
end
local function move_nodes(pos, lpos1, lpos2, handover)
local meta = M(pos)
local owner = meta:get_string("owner")
local max_speed = meta:contains("max_speed") and meta:get_int("max_speed") or MAX_SPEED
if #lpos1 == #lpos2 then
for idx = 1, #lpos1 do
local pos1 = lpos1[idx]
local pos2 = lpos2[idx]
if not minetest.is_protected(pos1, owner) and not minetest.is_protected(pos2, owner) then
if is_simple_node(pos1) and is_air_like(pos2) then
move_node(pos, pos1, pos2, max_speed, handover)
else
if not is_simple_node(pos1) then
meta:set_string("status", S("No valid node at the start position"))
else
meta:set_string("status", S("No air at the destination position"))
end
end
else
if minetest.is_protected(pos1, owner) then
meta:set_string("status", S("Start position is protected"))
else
meta:set_string("status", S("Destination position is protected"))
end
return false
end
end
else
meta:set_string("status", S("Position list error"))
return false
end
local info = techage.get_node_info(handover)
if info and info.name == "techage:ta4_movecontroller" then
local mem = techage.get_mem(info.pos)
mem.num_entities = #lpos1
end
return true
end
local function moveon_nodes(pos, lpos1, lpos2, handover)
local meta = M(pos)
local owner = meta:get_string("owner")
local max_speed = meta:contains("max_speed") and meta:get_int("max_speed") or MAX_SPEED
if #lpos1 == #lpos2 then
for idx = 1, #lpos1 do
local pos1 = lpos1[idx]
local pos2 = lpos2[idx]
if not minetest.is_protected(pos1, owner) and not minetest.is_protected(pos2, owner) then
if is_air_like(pos2) then
local dir = determine_dir(pos1, pos2)
local obj = capture_entity(pos1)
if obj then
obj:get_luaentity().handover = handover
move_entity(obj, pos2, dir, max_speed)
end
else
if not is_simple_node(pos1) then
meta:set_string("status", S("No valid node at the start position"))
else
meta:set_string("status", S("No air at the destination position"))
end
end
else
if minetest.is_protected(pos1, owner) then
meta:set_string("status", S("Start position is protected"))
else
meta:set_string("status", S("Destination position is protected"))
end
return false
end
end
else
meta:set_string("status", S("Position list error"))
return false
end
local info = techage.get_node_info(handover)
if info and info.name == "techage:ta4_movecontroller" then
local mem = techage.get_mem(info.pos)
mem.num_entities = #lpos1
end
return true
end
local function move_to_other_pos(pos)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
if nvm.pos2 then
local lpos1 = nvm.lpos1 or {}
local lpos2 = nvm.lpos2 or {}
nvm.pos2 = false
local handover = meta:contains("handoverA") and meta:get_string("handoverA")
return move_nodes(pos, lpos2, lpos1, handover)
else
local lpos1 = nvm.lpos1 or {}
local lpos2 = nvm.lpos2 or {}
nvm.pos2 = true
local handover = meta:contains("handoverB") and meta:get_string("handoverB")
return move_nodes(pos, lpos1, lpos2, handover)
end
end
local function takeover(pos)
local meta = M(pos)
local nvm = techage.get_nvm(pos)
local mem = techage.get_mem(pos)
mem.entities_are_there = nil
if nvm.pos2 then
local lpos1 = nvm.lpos1 or {}
local lpos2 = nvm.lpos2 or {}
nvm.pos2 = false
local handover = meta:contains("handoverA") and meta:get_string("handoverA")
return moveon_nodes(pos, lpos2, lpos1, handover)
else
local lpos1 = nvm.lpos1 or {}
local lpos2 = nvm.lpos2 or {}
nvm.pos2 = true
local handover = meta:contains("handoverB") and meta:get_string("handoverB")
return moveon_nodes(pos, lpos1, lpos2, handover)
end
end
minetest.register_node("techage:ta4_movecontroller", {
description = S("TA4 Move Controller"),
tiles = {
-- up, down, right, left, back, front
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4_top.png",
"techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_movecontroller.png",
},
after_place_node = function(pos, placer, itemstack)
local meta = M(pos)
techage.logic.after_place_node(pos, placer, "techage:ta4_movecontroller", S("TA4 Move Controller"))
techage.logic.infotext(meta, S("TA4 Move Controller"))
local nvm = techage.get_nvm(pos)
meta:set_string("formspec", formspec(nvm, meta))
end,
on_receive_fields = function(pos, formname, fields, player)
if minetest.is_protected(pos, player:get_player_name()) then
return
end
local meta = M(pos)
local nvm = techage.get_nvm(pos)
if fields.record then
nvm.lpos1 = nil
nvm.lpos2 = nil
nvm.pos2 = false
meta:set_string("status", S("Recording..."))
local name = player:get_player_name()
minetest.chat_send_player(name, S("Click on all blocks that shall be moved"))
MarkedNodes[name] = {}
meta:set_string("formspec", formspec(nvm, meta))
elseif fields.ready then
local name = player:get_player_name()
local pos_list = get_poslist(name)
local text = #pos_list.." "..S("block positions are stored.")
meta:set_string("status", text)
meta:set_string("distance", fields.distance)
nvm.lpos1 = pos_list
nvm.lpos2 = table_add(pos_list, to_vector(fields.distance))
nvm.pos2 = false
unmark_all(name)
meta:set_string("formspec", formspec(nvm, meta))
elseif fields.store then
meta:set_string("distance", fields.distance)
nvm.lpos2 = table_add(nvm.lpos1, to_vector(fields.distance))
nvm.pos2 = false
meta:set_string("formspec", formspec(nvm, meta))
elseif fields.moveAB then
meta:set_string("status", "")
nvm.pos2 = false
if move_to_other_pos(pos) then
meta:set_string("formspec", formspec(nvm, meta))
local name = player:get_player_name()
MarkedNodes[name] = nil
end
elseif fields.moveBA then
meta:set_string("status", "")
nvm.pos2 = true
if move_to_other_pos(pos) then
meta:set_string("formspec", formspec(nvm, meta))
local name = player:get_player_name()
MarkedNodes[name] = nil
end
end
end,
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
ta4_formspec = WRENCH_MENU,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
local INFO = [[Commands: 'a2b', 'b2a', 'move']]
techage.register_node({"techage:ta4_movecontroller"}, {
on_recv_message = function(pos, src, topic, payload)
if topic == "info" then
return INFO
elseif topic == "a2b" then
local nvm = techage.get_nvm(pos)
nvm.pos2 = false
return move_to_other_pos(pos)
elseif topic == "b2a" then
local nvm = techage.get_nvm(pos)
nvm.pos2 = true
return move_to_other_pos(pos)
elseif topic == "move" then
return move_to_other_pos(pos)
elseif topic == "handover" then
return takeover(pos)
end
return false
end,
})
minetest.register_craft({
output = "techage:ta4_movecontroller",
recipe = {
{"techage:aluminum", "group:wood","techage:aluminum"},
{"default:mese_crystal_fragment", "techage:ta4_wlanchip", "default:mese_crystal_fragment"},
{"group:wood", "basic_materials:gear_steel", "group:wood"},
},
})
minetest.register_on_joinplayer(function(player)
unlock_player(player)
end)
minetest.register_on_leaveplayer(function(player)
if unlock_player(player) then
detach_player(player)
end
end)
minetest.register_on_dieplayer(function(player)
if unlock_player(player) then
detach_player(player)
end
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B