commit 356eb427fb8f15da9fe26f79c28dd1b36acb9d13 Author: runs Date: Wed Aug 18 00:55:45 2021 +0200 first commit diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..efb5d1d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,4 @@ +# Licenses + +- Source code: GPLv3. +- Textures: CC BY-SA 4.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..6512c11 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Errand Merchands + +Merchants to trading with. + +## Licenses + +- Code: GPL v3.0 +- Textures: CC BY-SA 4.0 diff --git a/api.lua b/api.lua new file mode 100644 index 0000000..a8fbc68 --- /dev/null +++ b/api.lua @@ -0,0 +1,521 @@ +local S = ... + +--Helper Funtions + +local function is_table_empty(_table) + return next(_table) == nil +end + +local function is_srt_empty(s) + return s == nil or s == '' +end + +local function round(x) + return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5) +end + +local function boolean_to_string(var) + if var or var == 1 or var == "true" then + return "true" + elseif not(var) or var == nil or var == 0 or var == "false" then + return "false" + else + return "false" + end +end + +eraz.file_exists = function(filename) + local f = io.open(filename, "r") + if f ~= nil then + io.close(f) + return true + else + return false + end +end + +--CORE functions + +local function set_gender() + local gender + if math.random(2) == 1 then + gender = "male" + else + gender = "female" + end + return gender +end + +local function set_cloths() + local cloths = { + head = nil, + upper = "eraz:adventurer_jacket", + lower = nil, + footwear = "eraz:adventurer_boots", + } + return cloths +end + +local function set_loot() + if is_table_empty(eraz.loots) then + return nil + end + local keyset = {} + for k in pairs(eraz.loots) do + table.insert(keyset, k) + end + local loot = keyset[math.random(#keyset)] + --minetest.chat_send_all(loot) + return loot +end + +local function load_loot(merchant) + local loot = eraz.loots[merchant.loot] + return loot +end + +local function update_nick(self) + if self.show_name and self.nick and not(self.nick == "") then + self.object:set_nametag_attributes({ + text = self.nick, + bgcolor = "#FFFFFF00", + }) + else + self.object:set_nametag_attributes({ + text = "", + }) + end +end + +local function compose_cloth(_base_texture, cloths) + local base_texture = player_api.compose_base_texture(_base_texture, { + canvas_size ="128x64", + skin_texture = "player_skin.png", + eyebrowns_pos = "16,16", + eye_right_pos = "18,24", + eye_left_pos = "26,24", + mouth_pos = "16,28", + hair_preview = false, + hair_pos = "0,0", + }) + local cloth = base_texture.."^".."[combine:128x64:0,0=" + if cloths.head then + cloth = cloth..":"..player_api.cloth_pos[1].."=" + ..minetest.registered_craftitems[cloths.head]._cloth_texture + end + if cloths.upper then + cloth = cloth..":"..player_api.cloth_pos[2].."=" + ..minetest.registered_craftitems[cloths.upper]._cloth_texture + end + if cloths.lower then + cloth = cloth..":"..player_api.cloth_pos[3].."=" + ..minetest.registered_craftitems[cloths.lower]._cloth_texture + end + if cloths.footwear then + cloth = cloth..":"..player_api.cloth_pos[4].."=" + ..minetest.registered_craftitems[cloths.footwear]._cloth_texture + end + return cloth +end + +function eraz.set_initial_properties(self, staticdata, dtime_s) + if dtime_s == 0 then --new merchant + --self.type already defined when spawned + self.nick = S("Merchant") + self.gender = mobkit.remember(self, "gender", set_gender()) + local base_texture = player_api.create_base_texture(self.gender) + self.base_texture = mobkit.remember(self, "base_texture", minetest.serialize(base_texture)) + local cloths = set_cloths() + local cloth = compose_cloth(base_texture, cloths) + self.cloth = mobkit.remember(self, "cloth", cloth) + self.loot = mobkit.remember(self, "loot", set_loot()) + self.show_name = mobkit.remember(self, "show_name", false) + else + self.cloth = mobkit.recall(self, "cloth") + self.base_texture = mobkit.recall(self, "base_texture") + self.gender = mobkit.recall(self, "gender") + self.loot = mobkit.recall(self, "loot") + self.type = mobkit.recall(self, "type") + self.nick = mobkit.recall(self, "nick") or S("Merchant") + self.show_name = mobkit.recall(self, "show_name") or false + end + local model + if self.gender == "female" then + model = "female.b3d" + else + model = "character.b3d" + end + self.trading = mobkit.remember(self, "trading", false) + self.object:set_properties({ + visual = "mesh", + mesh = model, + textures = {self.cloth}, + }) + if self.type == "fixed" then + self:stand() + end + update_nick(self) +end + +function eraz.register_egg(name, desc, inv_img) + local description = S("@1", desc) + minetest.register_craftitem(name.."_set", { + description = description, + inventory_image = inv_img, + groups = {spawn_egg = 2}, + stack_max = 1, + on_place = function(itemstack, placer, pointed_thing) + local spawn_pos = pointed_thing.above + local under = minetest.get_node(pointed_thing.under) + local def = minetest.registered_nodes[under.name] + if def and def.on_rightclick then + return def.on_rightclick(pointed_thing.under, under, placer, itemstack) + end + if spawn_pos and not minetest.is_protected(spawn_pos, placer:get_player_name()) then + if not minetest.registered_entities[name] then + return + end + local meta = itemstack:get_meta() + local staticdata = meta:get_string("staticdata") + local ent = minetest.add_entity(spawn_pos, name, staticdata) + local ent_ref = ent:get_luaentity() + local merchant_type + local controls = placer:get_player_control() + if controls.aux1 then + merchant_type = "fixed" + else + merchant_type = "errand" + end + ent_ref.type = mobkit.remember(ent_ref, "type", merchant_type) + if merchant_type == "fixed" then + ent_ref:stand() + end + itemstack:take_item() + end + end, + }) +end + +function eraz.cancel_trading(self, msg) + local player_name = self.trading + if player_name then + local player = minetest.get_player_by_name(player_name) + if player then + minetest.close_formspec(player_name, "eraz:merchant") + if msg then + minetest.chat_send_player(player_name, msg) + end + end + end +end + +local _contexts = {} + +local function get_context(name) + local context = _contexts[name] or {} + _contexts[name] = context + return context +end + +minetest.register_on_leaveplayer(function(player) + _contexts[player:get_player_name()] = nil +end) + +local function get_items_loot(loot) + local items_loot = "" + local items_count = 0 + local col = 0 + local row = 0 + local amount, price + for item, item_def in pairs(eraz.loots[loot].items) do + amount = tostring(item_def.amount or 1) + price = tostring(item_def.price or 0)..elez.coin_symbol + items_loot = items_loot.. + "item_image_button["..tostring(col)..","..tostring(row)..";1,1;"..item_def.name..";"..item..";"..amount.."/"..price.."]" + items_count = items_count + 1 + if col == 2 then + col = 0 + row = row + 1 + else + col = col + 1 + end + end + return items_loot, items_count +end + +local function get_items_basket(player) + local items_basket = "" + local total_cost = 0 + local player_name = player:get_player_name() + local context = get_context(player_name) + if is_table_empty(context.basket) then + return items_basket, total_cost + end + local col = 0 + local row = 0 + local amount, price + for item, item_def in pairs(context.basket) do + amount = tostring(item_def.amount or 1) + price = tostring(item_def.price or 0)..elez.coin_symbol + items_basket = items_basket.. + "item_image_button["..tostring(col)..","..tostring(row) + ..";1,1;"..item_def.name..";"..item..";"..amount.."/"..price.."]" + total_cost = total_cost + item_def.price + if col == 2 then + col = 0 + row = row + 1 + else + col = col + 1 + end + end + context.total_cost = total_cost + return items_basket, total_cost +end + +local function compose_edit_formspec(self, player) + local player_name = player:get_player_name() + local context = get_context(player_name) + local loots = "" + local index + local i = 1 + for loot_name in pairs(eraz.loots) do + if i > 1 then + loots = loots.."," + end + loots = loots..loot_name + if loot_name == context.merchant.loot then + index = i + end + i = i + 1 + end + if not index then + index = 0 + end + local selected = "false" + if context.merchant.type == "fixed" then + selected = "true" + end + local formspec = [[ + formspec_version[4] + size[6,6] + set_focus[btn_close;] + field[0.5,0.5;3,0.5;ipt_nick;]]..S("Name")..":"..";"..context.merchant.nick..[[] + checkbox[3.75,0.75;chk_show_name;]]..S("Show name")..";"..boolean_to_string(context.merchant.show_name)..[[] + label[0.5,2;]]..S("Loot")..":"..[[] + dropdown[1.5,1.5;3;drpdwn;]]..loots..[[;]]..tostring(index)..[[;] + checkbox[0.5,3;chk_fixed;]]..S("Fixed")..[[;]]..selected..[[] + style[btn_remove;bgcolor=indianred] + button_exit[0.5,3.5;1,1;btn_remove;]]..S("Remove")..[[] + button_exit[2.5,4.5;1,1;btn_close;]]..S("Close")..[[] + ]] + return formspec +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "eraz:edit_merchant" then + return + end + local player_name = player:get_player_name() + local context = get_context(player_name) + if fields.drpdwn then + context.merchant.loot = mobkit.remember(context.merchant, "loot", fields.drpdwn) + end + if fields.chk_fixed then + local is_fixed = minetest.is_yes(fields.chk_fixed) + local _type + if is_fixed then + _type = "fixed" + else + _type = "errand" + end + context.merchant.type = mobkit.remember(context.merchant, "type", _type) + if is_fixed then + context.merchant:stand() + end + end + if fields.ipt_nick then + context.merchant.nick = mobkit.remember(context.merchant, "nick", fields.ipt_nick) + update_nick(context.merchant) + end + if fields.chk_show_name then + context.merchant.show_name = mobkit.remember(context.merchant, "show_name", + minetest.is_yes(fields.chk_show_name)) + update_nick(context.merchant) + end + if fields.btn_remove then + if context.merchant.trading then + minetest.close_formspec(player_name, "eraz:merchant") + end + eraz.cancel_trading(context.merchant, S("The merchant has been removed.")) + context.merchant.object:remove() + end +end) + +local function compose_formspec(self, player, msg) + local money = tostring(elez.get_money(player)).." "..elez.coin_symbol + local items_loot, items_count = get_items_loot(self.loot) + local items_basket, total_cost = get_items_basket(player) + total_cost = tostring(total_cost).." "..elez.coin_symbol + local items_count_str = tostring(items_count) + if is_srt_empty(msg) then + msg = "" + end + local face = player_api.get_face(minetest.deserialize(self.base_texture), 1.0, true) + local formspec = [[ + formspec_version[4] + size[8.25,8.25] + image[0.5,0.25;0.5,0.5;]]..face..[[] + label[1.25,0.5;]]..self.nick..[[] + label[0.5,1;]]..items_count_str.." "..S("items(s)") + .." | "..S("Amount/Price")..[[] + label[0.5,4.5;]]..S("Click to add to the basket")..[[] + label[4.25,4.5;]]..S("Click to delete from the basket")..[[] + label[5.25,0.75;]]..S("Money")..":".." "..money..[[] + image[4.5,0.625;0.5,0.5;eraz_basket.png] + label[5.25,1;]]..S("Cost")..":".." "..total_cost..[[] + box[0.5,1.25;3,3;darkgray] + scroll_container[0.5,1.25;3,3;scroll_buy;vertical;] + ]]..items_loot..[[ + scroll_container_end[] + scrollbaroptions[min=0;max=]]..tostring(round(items_count)) + ..[[;smallstep=]]..items_count_str..[[;largestep=]]..items_count_str..[[] + style_type[scroll_buy;bgcolor=#446699] + scrollbar[3.5,1.25;0.5,3;vertical;scroll_buy;0] + box[4.25,1.25;3,3;darkgray] + scroll_container[4.25,1.25;3,3;scroll_basket;vertical;] + ]]..items_basket..[[ + scroll_container_end[] + scrollbaroptions[min=0;max=]]..tostring(round(items_count)) + ..[[;smallstep=]]..items_count_str..[[;largestep=]]..items_count_str..[[] + scrollbar[7.25,1.25;0.5,3;vertical;scroll_basket;0] + button_exit[5.125,5;1,1;btn_buy;]]..S("Buy")..[[] + label[0.5,6.5;]]..msg..[[] + button_exit[3.25,6.75;1,1;btn_close;]]..S("Close")..[[] + ]] + return formspec +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "eraz:merchant" then + return + end + local player_name = player:get_player_name() + local context = get_context(player_name) + local item, item_def + local action + --minetest.chat_send_all(minetest.serialize(fields)) + for key, value in pairs(fields) do + if not(key=="scroll_buy") and not(key=="scroll_basket") and not(key=="quit") + and not(key=="btn_close") and not(key=="btn_buy") then + item = key + if string.sub(key, 1, 8) == "_BASKET_" then + action = "delete" + item_def = context.basket[item] + else + action = "buy" + item_def = eraz.loots[context.merchant.loot].items[key] + end + break + end + end + if item then + if action == "buy" then + item = "_BASKET_"..item + if context.basket[item] then --already in the basket=>only increase amount + context.basket[item].amount = context.basket[item].amount + item_def.amount + context.basket[item].price = context.basket[item].price + item_def.price + else --new + context.basket[item] = { + name = item_def.name, + price = item_def.price, + amount = item_def.amount, + } + end + else --action = "delete" + context.total_cost = context.total_cost - context.basket[item].price + context.basket[item] = nil + end + minetest.show_formspec(player_name, "eraz:merchant", + compose_formspec(context.merchant, player)) + return + end + if fields.btn_buy then + local msg + if context.total_cost <= 0 then + msg = S("You have not selected products to buy!") + elseif elez.get_money(player) < context.total_cost then + msg = S("You have not enough money!") + else + local inv = player:get_inventory() + local item_stack + local cost = 0 + for _item, _item_def in pairs(context.basket) do + item_stack = ItemStack(_item_def.name.." "..tostring(_item_def.amount)) + if inv:room_for_item("main", item_stack) then + inv:add_item("main", item_stack) + context.basket[_item] = nil + elez.add_money(player, -_item_def.price) + cost = cost + _item_def.price + context.total_cost = context.total_cost - _item_def.price + end + end + msg = S("You have purchased products with a value of").." " + ..tostring(cost).." "..elez.coin_symbol + end + minetest.show_formspec(player_name, "eraz:merchant", + compose_formspec(context.merchant, player, msg)) + return + end + context.merchant.trading = false +end) + +local function load_shop(self, player) + local player_name = player:get_player_name() + local loot = load_loot(self) + if not loot then + minetest.chat_send_player(player_name, S("This merchant has not loot.")) + return + end + local context = get_context(player_name) + context.merchant = self + context.basket = {} + context.total_cost = 0 + minetest.show_formspec(player_name, "eraz:merchant", + compose_formspec(self, player)) + self.trading = player_name + if self.type == "errand" then + self:stand() --stop merchant + end +end + +local function edit_merchant(self, player) + local player_name = player:get_player_name() + local context = get_context(player_name) + context.merchant = self + minetest.show_formspec(player_name, "eraz:edit_merchant", + compose_edit_formspec(self, player)) +end + +function eraz.register_loot(name, def) + if not eraz.loots[name] then + eraz.loots[name] = def + end +end + +function eraz.on_rightclick(self, player) + local controls = player:get_player_control() + local has = minetest.check_player_privs(player, { + server = true, + }) + if has and controls.aux1 then + edit_merchant(self, player) + return + end + if self.trading then + minetest.chat_send_player(player:get_player_name(), S("This merchant is already trading with another player.")) + return + else + load_shop(self, player) + return + end +end diff --git a/brain_merchant.lua b/brain_merchant.lua new file mode 100644 index 0000000..5c9355b --- /dev/null +++ b/brain_merchant.lua @@ -0,0 +1,37 @@ +-- The Brain of the Merchant +-- + +function eraz.merchant_brain(self) + + --local pos = self.object:get_pos() + + mobkit.vitals(self) + + if self.hp <= 0 then + self:on_die() + return + end + + if mobkit.timer(self, 1) then + + local prty = mobkit.get_queue_priority(self) + + if prty < 30 then + if self.isinliquid then + mobkit.hq_liquid_recovery(self, 30) + return + end + end + + --local player = mobkit.get_nearby_player(self) + + --minetest.chat_send_all("test") + + --Wandering default + if mobkit.is_queue_empty_high(self) and self.type == "errand" + and not(self.trading) then + mobkit.hq_roam(self, 0) + end + + end +end diff --git a/cloth.lua b/cloth.lua new file mode 100644 index 0000000..998ae68 --- /dev/null +++ b/cloth.lua @@ -0,0 +1,25 @@ +local S = ... + +--Adventurer Jacket + +player_api.register_cloth("eraz:adventurer_jacket", { + description = S("Adventurer Jacket"), + texture = "eraz_adventurer_jacket.png", + inventory_image = "eraz_adventurer_jacket_inv.png", + wield_image = "eraz_adventurer_jacket_inv.png", + preview = "eraz_adventurer_jacket_preview.png", + gender = "unisex", + groups = {cloth = 2}, +}) + +--Adventurer Boots + +player_api.register_cloth("eraz:adventurer_boots", { + description = S("Adventurer Boots"), + texture = "eraz_adventurer_boots.png", + inventory_image = "eraz_adventurer_boots_inv.png", + wield_image = "eraz_adventurer_boots_inv.png", + preview = "eraz_adventurer_boots_preview.png", + gender = "unisex", + groups = {cloth = 4}, +}) diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..4e385f5 --- /dev/null +++ b/init.lua @@ -0,0 +1,20 @@ +-- +-- Eraz +-- + +eraz = {} +eraz.loots = {} + +local modname = minetest.get_current_modname() +local modpath = minetest.get_modpath(modname) +local S = minetest.get_translator(modname) + +assert(loadfile(modpath .. "/api.lua"))(S) +assert(loadfile(modpath .. "/loots.lua"))() +assert(loadfile(modpath .. "/brain_merchant.lua"))() +assert(loadfile(modpath .. "/merchant.lua"))(S) +assert(loadfile(modpath .. "/cloth.lua"))(S) +local user_file = modpath .. "/user.lua" +if eraz.file_exists(user_file) then + assert(loadfile(user_file))() +end diff --git a/locale/eraz.es.tr b/locale/eraz.es.tr new file mode 100644 index 0000000..2ec92fc --- /dev/null +++ b/locale/eraz.es.tr @@ -0,0 +1,24 @@ +# textdomain: eraz +Merchant=Mercader +Money=Dinero +Cost=Coste +item(s)=producto(s) +Amount/Price=Cantidad/Precio +Click to add to the basket=Clic para añadir a la cesta +Click to delete from the basket=Clic para eliminar de la cesta +Buy=Comprar +Close=Cerrar +Name=Nombre +Loot=Lote +Fixed=Estático +Remove=Eliminar +Show name=Mostrar nombre +This merchant has not loot.=Este mercader no tiene nada que vender. +The merchant has been removed.=El mercader ha sido eliminado. +The merchant has died.=El mercader ha muerto. +This merchant is already trading with another player.=Este mercader ya está comerciando con otro jugador. +You have purchased products with a value of=Has comprado productos por valor de +You have not enough money!=¡No tienes suficiente dinero! +You have not selected products to buy!=No has seleccionado productos para comprar +Adventurer Jacket=Chaqueta de aventurero +Adventurer Boots=Botas de aventurero diff --git a/loots.lua b/loots.lua new file mode 100644 index 0000000..33f1941 --- /dev/null +++ b/loots.lua @@ -0,0 +1,15 @@ +eraz.register_loot("basic_food", { + type = "fixed", + items = { + apple = { + name = "default:apple", + price = 3, + amount = 6, + }, + bread = { + name = "farming:bread", + price = 1, + amount = 1, + }, + }, +}) diff --git a/merchant.lua b/merchant.lua new file mode 100644 index 0000000..120803a --- /dev/null +++ b/merchant.lua @@ -0,0 +1,64 @@ +-- +-- ERRAND MERCHANTS +-- +local S = ... + +minetest.register_entity("eraz:merchant",{ + type = "errand", + genre = "male", + visual = "mesh", + mesh = "character.b3d", + textures = {"character.png"}, + visual_size = {x= 1.0, y= 1.0}, + collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}, + static_save = true, + physical = true, + collide_with_objects = true, + -- mod props + + -- mobkit api props + get_staticdata = mobkit.statfunc, + stepheight = 0.1, --EVIL! + springiness = 0, + buoyancy = 0.5, -- portion of hitbox submerged + max_speed = 3, + jump_height = 1, + view_range = 10, + lung_capacity = 10, -- seconds + max_hp = 20, + + attack = {range=0.5, damage_groups = {fleshy=3}}, + + animation = { + stand = {range= { x= 0, y= 79}, speed= 5, loop= true}, + walk = {range= { x= 168, y= 187}, speed= 25, loop= true}, + }, + + logic = eraz.merchant_brain, + + on_activate = function(self, staticdata, dtime_s) --on_activate, required + mobkit.actfunc(self, staticdata, dtime_s) + eraz.set_initial_properties(self, staticdata, dtime_s) + end, + + on_step = function(self, dtime) + mobkit.stepfunc(self, dtime) -- required + end, + + on_rightclick = function(self, clicker) + eraz.on_rightclick(self, clicker) + end, + + stand = function(self) + mobkit.clear_queue_low(self) + mobkit.clear_queue_high(self) + mobkit.animate(self, "stand") + end, + + on_die = function(self) + eraz.cancel_trading(self, S("The merchant has died.")) + end, + +}) + +eraz.register_egg("eraz:merchant", S("Merchant"), "eraz_spawnegg_merchant.png", false) diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..0f6d96a --- /dev/null +++ b/mod.conf @@ -0,0 +1,3 @@ +name = eraz +description = Errand Merchants +depends = player_api diff --git a/textures/eraz_adventurer_boots.png b/textures/eraz_adventurer_boots.png new file mode 100644 index 0000000..d5b73f8 Binary files /dev/null and b/textures/eraz_adventurer_boots.png differ diff --git a/textures/eraz_adventurer_boots_inv.png b/textures/eraz_adventurer_boots_inv.png new file mode 100644 index 0000000..dc1a6be Binary files /dev/null and b/textures/eraz_adventurer_boots_inv.png differ diff --git a/textures/eraz_adventurer_boots_preview.png b/textures/eraz_adventurer_boots_preview.png new file mode 100644 index 0000000..8ae4bd2 Binary files /dev/null and b/textures/eraz_adventurer_boots_preview.png differ diff --git a/textures/eraz_adventurer_jacket.png b/textures/eraz_adventurer_jacket.png new file mode 100644 index 0000000..c113210 Binary files /dev/null and b/textures/eraz_adventurer_jacket.png differ diff --git a/textures/eraz_adventurer_jacket_inv.png b/textures/eraz_adventurer_jacket_inv.png new file mode 100644 index 0000000..067d439 Binary files /dev/null and b/textures/eraz_adventurer_jacket_inv.png differ diff --git a/textures/eraz_adventurer_jacket_preview.png b/textures/eraz_adventurer_jacket_preview.png new file mode 100644 index 0000000..0bc4be4 Binary files /dev/null and b/textures/eraz_adventurer_jacket_preview.png differ diff --git a/textures/eraz_basket.png b/textures/eraz_basket.png new file mode 100644 index 0000000..8e21c33 Binary files /dev/null and b/textures/eraz_basket.png differ diff --git a/textures/eraz_spawnegg_merchant.png b/textures/eraz_spawnegg_merchant.png new file mode 100644 index 0000000..3e8599e Binary files /dev/null and b/textures/eraz_spawnegg_merchant.png differ