From eb158f6f1525d8e72ac04e20ac84a07f8610acf5 Mon Sep 17 00:00:00 2001 From: zorman2000 Date: Sat, 12 Nov 2016 07:06:09 -0500 Subject: [PATCH] First commit. Using mobs_npc code as start. --- .buildpath | 4 + .project | 17 ++ .settings/org.eclipse.ldt.prefs | 2 + depends.txt | 3 + description.txt | 1 + init.lua | 30 +++ license.txt | 21 ++ locale/de.txt | 23 +++ locale/template.txt | 21 ++ mod.conf | 1 + npc.lua | 324 +++++++++++++++++++++++++++++ readme.md | 11 + textures/mobs_npc.png | Bin 0 -> 901 bytes textures/mobs_npc2.png | Bin 0 -> 1018 bytes textures/mobs_npc_baby.png | Bin 0 -> 684 bytes textures/mobs_trader.png | Bin 0 -> 783 bytes textures/mobs_trader2.png | Bin 0 -> 783 bytes textures/mobs_trader3.png | Bin 0 -> 779 bytes trader.lua | 355 ++++++++++++++++++++++++++++++++ trader_test.lua | 264 ++++++++++++++++++++++++ 20 files changed, 1077 insertions(+) create mode 100644 .buildpath create mode 100644 .project create mode 100644 .settings/org.eclipse.ldt.prefs create mode 100755 depends.txt create mode 100755 description.txt create mode 100755 init.lua create mode 100755 license.txt create mode 100755 locale/de.txt create mode 100755 locale/template.txt create mode 100755 mod.conf create mode 100755 npc.lua create mode 100755 readme.md create mode 100755 textures/mobs_npc.png create mode 100755 textures/mobs_npc2.png create mode 100755 textures/mobs_npc_baby.png create mode 100755 textures/mobs_trader.png create mode 100755 textures/mobs_trader2.png create mode 100755 textures/mobs_trader3.png create mode 100755 trader.lua create mode 100755 trader_test.lua diff --git a/.buildpath b/.buildpath new file mode 100644 index 0000000..15fa7e6 --- /dev/null +++ b/.buildpath @@ -0,0 +1,4 @@ + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..23720f2 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + advanced_npc + + + + + + org.eclipse.dltk.core.scriptbuilder + + + + + + org.eclipse.ldt.nature + + diff --git a/.settings/org.eclipse.ldt.prefs b/.settings/org.eclipse.ldt.prefs new file mode 100644 index 0000000..3b1203d --- /dev/null +++ b/.settings/org.eclipse.ldt.prefs @@ -0,0 +1,2 @@ +Grammar__default_id=lua-5.1 +eclipse.preferences.version=1 diff --git a/depends.txt b/depends.txt new file mode 100755 index 0000000..a3172c8 --- /dev/null +++ b/depends.txt @@ -0,0 +1,3 @@ +default +mobs +intllib? diff --git a/description.txt b/description.txt new file mode 100755 index 0000000..3936b7f --- /dev/null +++ b/description.txt @@ -0,0 +1 @@ +Adds simple NPC and Trader. diff --git a/init.lua b/init.lua new file mode 100755 index 0000000..7b46eeb --- /dev/null +++ b/init.lua @@ -0,0 +1,30 @@ + +local path = minetest.get_modpath("mobs_npc") + +-- Intllib +local S +if minetest.get_modpath("intllib") then + S = intllib.Getter() +else + S = function(s, a, ...) + if a == nil then + return s + end + a = {a, ...} + return s:gsub("(@?)@(%(?)(%d+)(%)?)", + function(e, o, n, c) + if e == ""then + return a[tonumber(n)] .. (o == "" and c or "") + else + return "@" .. o .. n .. c + end + end) + end +end +mobs.intllib = S + +-- NPC +dofile(path .. "/npc.lua") -- TenPlus1 +dofile(path .. "/trader.lua") + +print (S("[MOD] Mobs Redo 'NPCs' loaded")) diff --git a/license.txt b/license.txt new file mode 100755 index 0000000..fec6f6a --- /dev/null +++ b/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 TenPlus1 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/locale/de.txt b/locale/de.txt new file mode 100755 index 0000000..cffa840 --- /dev/null +++ b/locale/de.txt @@ -0,0 +1,23 @@ +# German Translation for mobs_npc mod +# Deutsche Übersetzung der mobs_npc Mod +# last update: 2016/June/10 +# Author: Xanthin + +#init.lua +[MOD] Mobs Redo 'NPCs' loaded = [MOD] Mobs Redo 'NPCs' geladen + +#npc.lua +NPC dropped you an item for gold! = NSC ließ dir für Gold einen Gegenstand fallen! +NPC stands still. = NSC bleibt stehen. +NPC will follow you. = NSC wird dir folgen. +Npc = Nsc + +#trader.lua +Trader @1 = Händler @1 +[NPC] Hello, @2, have a look at my wares. = [NSC] Hallo, @2, wirf einen Blick auf meine Waren. +Trader @1's stock: = Händler @1s Warenlager +Selection = Auswahl +Price = Preis +Payment = Bezahlung +Bought items = Ware +Trader = Händler \ No newline at end of file diff --git a/locale/template.txt b/locale/template.txt new file mode 100755 index 0000000..906ddd2 --- /dev/null +++ b/locale/template.txt @@ -0,0 +1,21 @@ +# Template for translations of mobs_npc mod +# last update: 2016/June/10 + +#init.lua +[MOD] Mobs Redo 'NPCs' loaded = + +#npc.lua +NPC dropped you an item for gold! = +NPC stands still. = +NPC will follow you. = +Npc = + +#trader.lua +Trader @1 = +[NPC] Hello, @2, have a look at my wares. = +Trader @1's stock: = +Selection = +Price = +Payment = +Bought items = +Trader = \ No newline at end of file diff --git a/mod.conf b/mod.conf new file mode 100755 index 0000000..9646cf7 --- /dev/null +++ b/mod.conf @@ -0,0 +1 @@ +name = mobs_npc diff --git a/npc.lua b/npc.lua new file mode 100755 index 0000000..4ccd459 --- /dev/null +++ b/npc.lua @@ -0,0 +1,324 @@ + +local S = mobs.intllib + +-- Npc by TenPlus1 + +-- NPC Chat code by Zorman2000 +-- Chat system consists of chatline and options objects that are re-used to create a conversation. +-- The objects are the following: +-- +-- chatline = { text = "", options = {}, name = "", flag = "" } +-- 1. text: What the NPC says on this chat line +-- 2. options: What the player can answer to the chat line. Options are of another type of chat lines. +-- If you want the player to have no options to answer, set to nil +-- 3. name: The name of the NPC that will use this line. Use this when you want a specific NPC +-- to speak this line. +-- 4. flag: When the NPC speaks this line, this flag will be set into the entity's metadata. +-- +-- options = { opt = "", answer = { chatline } +-- 1. opt: The text that the player can say to the NPC +-- 2. answer: A chatline object (as described above) +-- +-- Example of conversation hierarchy +-- chat_options = { +-- chatline1 = { text = "Q1", options = { +-- option1 = { opt = "O1", answer = { +-- chatline = { text = "A1", options = nil } +-- } +-- }, +-- chatline2 = { text = "Q2", options = nil } +-- } + +local chat_options = { + { text = "Don't talk with me know, please", options = nil, name = "Angry Guy" }, + { text = "Hello, how are you doing?", options = { + { opt = "Good", answer = + { text = "That's good. Take care of yourself.", options = nil } + }, + { opt = "Great! And you?", answer = + { text = "I'm doing well, thank you. See ya around!", options = nil } + }, + { opt = "Not so well...", answer = + { text = "Hey, why not feeling good? What's wrong?", options = { + { opt = "Not your business!", answer = + { text = "So rude! Don't speak to me anymore!", options = nil, flag = "not_speak" } + }, + { opt = "It's nothing! But thank you for asking!", answer = + { text = "Ok my friend. See ya around!", options = nil } + }, + } + } + } + } + }, + { text = "I'm thinking of buying something but not sure...", options = nil }, + { text = "I have traveled around the world and only like this place...", options = nil } +} + + +local options = {"Question 1","Question 2","Question 3","Question 4"} + +--------------------------------------------------------------------- +-- Creates a formspec for dialog +-------------------------------------------------------------------- +local function create_formspec(options, close_option) + local options_length = table.getn(options) + 1 + local formspec_height = (options_length * 0.7) + 1 + local formspec = "size[7,"..tostring(formspec_height).."]" + for i, opt in ipairs(options) do + local y = 0.7; + if i > 1 then + y = (y * i) + end + formspec = formspec.."button[0.5,"..y..";6,0.5;opt"..tostring(i)..";"..options[i].opt.."]" + end + formspec = formspec.."button_exit[0.5,"..(formspec_height - 1)..";6,0.5;exit;"..close_option.."]" + return formspec +end + +--------------------------------------------------------------------- +-- Returns a random chatline for unimportant NPCs +--------------------------------------------------------------------- +local function get_random_chatline(chat_options) + local chat_options_length = table.getn(chat_options) + local random_option = math.random(1, chat_options_length - 1) + local found = false + while found == false do + for i,chatline in ipairs(chat_options) do + if i == random_option and chatline.name == nil then + found = true + return chatline + end + end + end +end + +--------------------------------------------------------------------- +-- Returns all chatlines for a specific NPC +--------------------------------------------------------------------- +local function get_chatline_for_npc(chat_options, npc_name) + local result = {} + for i,chatline in ipairs(chat_options) do + if chatline.name == npc_name then + table.insert(result, chatline) + end + end + return result +end + +----------------------------------------------------------------------------- +-- Functions for rotating NPC to look at player (taken from the API itself) +----------------------------------------------------------------------------- +local atan = function(x) + if x ~= x then + return 0 + else + return math.atan(x) + end +end + + +local function rotate_npc_to_player(self) + local s = self.object:getpos() + local objs = minetest.get_objects_inside_radius(s, 4) + local lp = nil + local yaw = 0 + + for n = 1, #objs do + if objs[n]:is_player() then + lp = objs[n]:getpos() + break + end + end + if lp then + local vec = { + x = lp.x - s.x, + y = lp.y - s.y, + z = lp.z - s.z + } + + yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate + + if lp.x > s.x then + yaw = yaw + math.pi + end + end + self.object:setyaw(yaw) +end + +--------------------------------------------------------------------- +-- Drives conversation +--------------------------------------------------------------------- +local function show_chat_option(npc_name, self, player_name, chat_options, close_option) + rotate_npc_to_player(self) + self.order = "stand" + + local chatline = get_random_chatline(chat_options) + minetest.chat_send_player(player_name, chatline.text) + if chatline.options ~= nil then + minetest.log("Current options: "..dump(chatline.options)) + local formspec = create_formspec(chatline.options, close_option) + minetest.show_formspec(player_name, "rndform", formspec) + end + + self.order = "follow" +end + + + + +mobs.npc_drops = { + "default:pick_steel", "mobs:meat", "default:sword_steel", + "default:shovel_steel", "farming:bread", "bucket:bucket_water" +} + +mobs:register_mob("mobs_npc:npc", { + type = "npc", + passive = false, + damage = 3, + attack_type = "dogfight", + attacks_monsters = true, + -- Added group attack + group_attack = true, + --pathfinding = true, + pathfinding = 1, + hp_min = 10, + hp_max = 20, + armor = 100, + collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35}, + visual = "mesh", + mesh = "character.b3d", + drawtype = "front", + textures = { + {"mobs_npc.png"}, + {"mobs_npc2.png"}, -- female by nuttmeg20 + }, + child_texture = { + {"mobs_npc_baby.png"}, -- derpy baby by AmirDerAssassine + }, + makes_footstep_sound = true, + sounds = {}, + -- Added walk chance + walk_chance = 30, + -- Added stepheight + stepheight = 1, + walk_velocity = 2, + run_velocity = 3, + jump = true, + drops = { + {name = "default:wood", chance = 1, min = 1, max = 3}, + {name = "default:apple", chance = 2, min = 1, max = 2}, + {name = "default:axe_stone", chance = 5, min = 1, max = 1}, + }, + water_damage = 0, + lava_damage = 2, + light_damage = 0, + follow = {"farming:bread", "mobs:meat", "default:diamond"}, + view_range = 15, + owner = "", + order = "follow", + --order = "stand", + fear_height = 3, + animation = { + speed_normal = 30, + speed_run = 30, + stand_start = 0, + stand_end = 79, + walk_start = 168, + walk_end = 187, + run_start = 168, + run_end = 187, + punch_start = 200, + punch_end = 219, + }, + on_rightclick = function(self, clicker) + + -- feed to heal npc + if mobs:feed_tame(self, clicker, 8, true, true) then + return + end + + local item = clicker:get_wielded_item() + local name = clicker:get_player_name() + + -- right clicking with gold lump drops random item from mobs.npc_drops + if item:get_name() == "default:gold_lump" then + + if not minetest.setting_getbool("creative_mode") then + item:take_item() + clicker:set_wielded_item(item) + end + + local pos = self.object:getpos() + + pos.y = pos.y + 0.5 + + minetest.add_item(pos, { + name = mobs.npc_drops[math.random(1, #mobs.npc_drops)] + }) + + minetest.chat_send_player(name, S("NPC dropped you an item for gold!")) + + return + end + + -- See chat + --show_chat_option(self.nametag, self, name, chat_options, "Nevermind") + + -- capture npc with net or lasso + mobs:capture_mob(self, clicker, 0, 5, 80, false, nil) + + -- by right-clicking owner can switch npc between follow and stand + if self.owner and self.owner == name then + + if self.order == "follow" then + self.order = "stand" + + minetest.chat_send_player(name, S("NPC stands still.")) + else + self.order = "follow" + + minetest.chat_send_player(name, S("NPC will follow you.")) + end + end + + end, +}) + +--mobs:register_spawn("mobs:npc", {"default:dirt_with_grass"}, 20, 0, 7000, 1, 31000) +--mobs:spawn_specific("mobs:npc", {"default:brick"}, {"air"}, 0, 15, 1, 1, 1, 0, 200, true) + +--mobs:register_spawn("mobs:npc", {"mg_villages:plotmarker"}, 20, 0, 1, 7000, 31000) +--mobs:spawn_specific("mobs:npc", {"mg_villages:plotmarker"}, {"air"}, 0, 15, 30, 1, 100, 0, 200, true) + +minetest.register_abm({ + label = "NPC spawning", + nodenames = {"mg_villages:plotmarker"}, + neighbors = {"air"}, + interval = 30, + chance = 25, + catch_up = false, + + action = function(pos, node, active_object_count, active_object_count_wider) + if active_object_count_wider > 20 then + return + end + minetest.log("Spawning NPC on: "..dump(pos.x)..", "..dump(pos.y + 1)..", "..dump(pos.z)) + minetest.log("Active Object count: "..dump(active_object_count)) + minetest.log("Wider object count: "..dump(active_object_count_wider)) + pos.y = pos.y + 1 + local mob = minetest.add_entity(pos, "mobs_npc:npc") + local ent = mob:get_luaentity() + + if not ent then + mob:remove() + return + end + end +}) + + +mobs:register_egg("mobs_npc:npc", S("Npc"), "default_brick.png", 1) + +-- compatibility +mobs:alias_mob("mobs:npc", "mobs_npc:npc") diff --git a/readme.md b/readme.md new file mode 100755 index 0000000..98bdf2a --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ + +NPC MOBS + + +NPC + +- While NPC's don't actually spawn in the world just yet, they do have a spawn egg available to drop him/her into the world and wander around defending himself if attacked. It will also he will help you attack any monsters in the area and will follow you if you hold a diamond. Right-clicking the NPC with a gold lump will make him drop steel tools or food, right-clicking with an empty hand orders the NPC to stay or follow if owned. + +Trader + +- Traders are new and still being tested but can be placed into the world using a spawn egg. Right-clicking on a trader opens his shop and allows you to buy his wares inside. If provoked a trader will attack a player or monster. diff --git a/textures/mobs_npc.png b/textures/mobs_npc.png new file mode 100755 index 0000000000000000000000000000000000000000..93563989580324581cac10eaa5a91c427eaac724 GIT binary patch literal 901 zcmV;01A6?4P)067o8=&K2l_nO4V-n|G)B{ptiksc002fl!)YT zauTX)vt?PX*QHpQtAo{27_Eh=elm_*5!U(mUnpU$)#7J>>-Fo4Be(e#Kx-v*{UZS4 zZnrN!eVvd0rIA(%q5ce@l(y3Rroo#OoDxn5BmOL6EuA(($zK3|FCzprQV97r@HlH{ zm2^;0NbQW`OsrJ87FH>1p~;c@aW={tYmmRPsHJmCYKeYwwKZ^^coFY78zHgLn5_Y% z6lh6w75dUBA?ru^zCUiakMHZToP2Q2ue+M6=C)FxuZ46k^*_!UmA9fcdTU7Z5?r*U zF-`&DZ3-66XRHDOk|_hdP@5Y3Q5dj!Ct{&5(1d2Y5f;59Yb~?@s?cxC$Y+8wD`%8( zj&FlTLs;vK+pl+w!DG;_Q-Jw+4!rL1-q&;a4*sQ?!Z3^tZ2yiShVvQkVVtIMxC6{U zIL8!T06aFP_&^Sk^AFJjcpQd%K*pwcer)$c=(GkTimIdcl3tfpK& zXw3khAQqpO6XPc$EUbPE1OzG!Fk(EHee)e4Gjtl6LBe`win(Ke)$gM(oPeEn2L3+= zc}8vni@h?yhLrohFo55qa)GEnu~FMJZQJ;kP7e)I#{+yPo5U=plpo0i&Dq={Jj)i3ekvLUQ?c>wU)9kyd?HXbb9NVJ!%#CueOw`|tk zd!{O}eeXG#_`^eTABK@hu@5RJ2caCG0)&nq>K_0l&=VHHggs&gr&G5=l?rYQFuIL# zcuJv*MaTxoDRqT-Ov8|9GZY$^s6#NuvK$$NZWpWJB82jl;y9wA%V`~VM~Y0Hy#3R!0{9|`}$00000NkvXXu0mjfYEy{n literal 0 HcmV?d00001 diff --git a/textures/mobs_npc2.png b/textures/mobs_npc2.png new file mode 100755 index 0000000000000000000000000000000000000000..a9d1a2c216d55d943556282f27e5ddb2ab381523 GIT binary patch literal 1018 zcmV3 zK#@>8fmK9ZUtE+^K5Jk(mQ+BWS45y#Kc-wnh-pWvUq_i~QiyL$P0n{DQ$O7iM;P?!Sh$-0Mo&h*6K#c>X^|Gk_H9=))R7*7$S0MuMH2Vzw7v0~l z@8vtV=w=Q=Fb>?0wNey;?{|sdQ%}V!1jLyV@x7)LY!@LnucZ;5O-NkOjbR8Cn+$C5 zT;H`!ZC2MyeI4pmJXQvP4ksu(oswQOGn2dnjA1ZWVP*{}1BaPJ@EuzBAiX|R*{0G=d81nB2UR32#$nBpZY@(MhWla$sQN)`#` zBNiY@qP!I#M|k4X8-^I07*qoM6N<$f=z4BN&o-= literal 0 HcmV?d00001 diff --git a/textures/mobs_npc_baby.png b/textures/mobs_npc_baby.png new file mode 100755 index 0000000000000000000000000000000000000000..e26e450112be808a83cd019069f2f94bef5db144 GIT binary patch literal 684 zcmV;d0#p5oP)X<)d}xq;TP>eC)Q1^|poezLfvNng7I*is0b>$eR7gq5jdR{{Q^x;Au_( z0004WQchCNq+tro zfL~wBw9!;+<+s+acMaYV3xK~L0K^zTfPV(^K7bEc03cKO1OO4Zx6c8ZyZ3Ijewu=| z-g`3eSrK*dNDAxa4FKhv8>w470wmX1cHI9B0Qw?SPi>%z?ZiO35+TBm0N}OBf5Ztk zc$4ck9~=Q7|IR>8-`^JiaM^Y49|15bs9EFr3jjPKHfu{*_9sAiY#vQ?0+@B*wrDez z5psZk)ZRoaW4{RQphs?XTuqL!02)oIrBR4IyRrzDa!e2Pd_M1Iz!a6*b=l83T6rO| z{{WJ~&}V?u_z{39foLxQ>X8T(t%)37q)^H;!1c*wnPt9$^P*OzE-U$P55hLmyFXlD z>}b0I_9}z5@3QJxf&CP7%nv4gmA;9{_CM0el|AHvQSC`xKY~13(U=lcU+n4G|!P zdjxn)0Hm-7U~-&_SsSzw-db`vfQ52v(&+T*ZTPI=0|01HQ!QqNRDtQz=7dHOCI9JI z;x;%V$fSYN(H?|UwM@hD0`ZJxQ%LOU)DFK=r928L9YxB`uD{soqf}zqb>u(qr!3KW SyUJSt00002 zB?3V!7E3GuP%;`ILmFB&09rd9DNrDLKmsUTB#%e~FKa5IR0FYE10#YOiDg2+Uji(Y z9?EC|+iwAnk&dOAX`P>(x2tp2t_7*9sKu#*)wYTD$q3=XlZYyq+40U<}uV0Zz*IZGCf3~J$?2(&0ZBLR*%f>1tLc9xtc>mOI&O7e(V#Ci0D zhJXK7S{>Q$&;`K$3(sly$*#u=bZw&t-&PpO+XyQ9bu)o!U zOM38*?lDWu`SWwhaWUCSOTk${4)$>k#6O#5Sx`TZn?XxSJe~?%{1AX@!?ckuh#7-3 z(h61{NEXQBV`O;>jCGXBXL8oCoSs9x9LP^k<)ro9H#_JLbUoTC-E^~V`mxRL0lQeV zI|EMFb^8ARp0Y5B@JkQj_af_zA!Dt~l zV~y6gfRIuMX`1@6@24pR31U*lE8;RYN^52K0+1|j0kU(S5&&aArPNE1N76nEtM`7$Oyo2bJ_sY6|mWixRAwIhK(!>2sT<5J0S=l zgl1KjO2X<5Kr}XO3V^lVI;G35R4wo1Ko`Iqf&fCA zuYki5f6(zL*WpmT2D}aGtYP$XnsUqeGTH&xC} z2Kzg8!%zV0)OB@U0n42(fcKA&_wp~oy`GlTy!B;k;quPLRfPLF{tKzeNI=)W4Lbk; N002ovPDHLkV1jXhRdfIV literal 0 HcmV?d00001 diff --git a/textures/mobs_trader2.png b/textures/mobs_trader2.png new file mode 100755 index 0000000000000000000000000000000000000000..cbd9b9367aa0d107c1573ce16a5e9be08afd5425 GIT binary patch literal 783 zcmV+q1MvKbP)2 zB?3V!7E3GuP%;`?H2_*W9ve|nA!KBQToh_#BPMZiiDg18gM%xMWQ~O{mya@!k&d93 zHMNu~Ij*jynQ5J$oVTlU)vg7ptEjuNKE$!gVP$vY;twDZu8R?mtM?L5?}D?)<3SkmE;k#i1X+P z4FMxU2mxh%eEv>$r-&#?fFucNDC>gfVC^rVp34ZoJ})tzRF3Ktt*H-navcsv!j_#pt*hG`>R5Hkj6 zq!p|@kSvhL$H?*&80#pL&*ZFOIX#DXIgp>8%2DgPZw}BO=z6qOy6I-!_+y*j0}iq1 za0Z;N>-7HtJY``N;g=r5??u)bL&jQ}%V{bMhpJJ4vi>^&hyrLI$RgfYlDQ#Pipey9 z#u}|}0U@Oj(m3|(z8}XFB#22FuZYXsD6N&@3qZ2C1<1~QN&u|;F{NIDJd!p_`H}%^ z?1>DJB~q8{#q#x)wibcVx3O4I*iz|Yfs6nQH>V9SUIE+f8W*xymtiZ*0)ma!#ZCwU z2%%ZkrIN6E0}!p7wgtdi@0`+QSE?3x62WRIL`u_l3uglE5`>dVL5@r26`%`X3PAuN zO;^Bt#2<7#$~Di`Yrxx}&KgF)iG(1Urnd`VKi9x{-WPSlrt5|@_%w8NzJZ2rYO0)@ z4EA^GhM@p9sq5;z0+u^n0Pi0k@8w^Fdp#|wdF#v8!sVTfs|fdV{1?6#OGkIi(DeWS N002ovPDHLkV1oMWWF7zj literal 0 HcmV?d00001 diff --git a/textures/mobs_trader3.png b/textures/mobs_trader3.png new file mode 100755 index 0000000000000000000000000000000000000000..e6b239adf78ea1b535531e83872250ce9202671c GIT binary patch literal 779 zcmV+m1N8ifP)D z85uk!0zoSlWg#F-EC5h48e22~U@{<_0L7?+)wYTE$pGQPlVdE6Y1l*Hqyz(o%Ms5X*zatdO` zpq(5AD-R?Ih?j0`;-6}`zfVff;>z+rF?Nf z27AH*T*A7zmz1xsv{3{?-^P-H!j{UE6xa#CQOmRerYm5#8*#zKScaV}3kY_`lypK6 zKnN{WQ!0s6ZvdjPX;%Pb%t0$tcIBD^%_5{)3X#(MHH9+)cL_qLQjm7ZyaG%C%pnLM zr1=V1KJbA)KIB@Knrpz@pw1e`yorP$n&-C*;JDPlWjPjg!?x>&H25@hb-sm$Zf>fa z8wbZbb;D2q+thV+UIFW!E`Z;6s@;;Bw{zK!aCv9rD#HC7{|DWkLwB8)lq~=N002ov JPDHLkV1mH2W8?q; literal 0 HcmV?d00001 diff --git a/trader.lua b/trader.lua new file mode 100755 index 0000000..169f445 --- /dev/null +++ b/trader.lua @@ -0,0 +1,355 @@ + +local S = mobs.intllib + +mobs.human = { + items = { + --{item for sale, price, chance of appearing in trader's inventory} + {"default:apple 10", "default:gold_ingot 2", 10}, + {"farming:bread 10", "default:gold_ingot 4", 5}, + {"default:clay 10", "default:gold_ingot 2", 12}, + {"default:brick 10", "default:gold_ingot 4", 17}, + {"default:glass 10", "default:gold_ingot 4", 17}, + {"default:obsidian 10", "default:gold_ingot 15", 50}, + {"default:diamond 1", "default:gold_ingot 5", 40}, + {"farming:wheat 10", "default:gold_ingot 2", 17}, + {"default:tree 5", "default:gold_ingot 4", 20}, + {"default:stone 10", "default:gold_ingot 8", 17}, + {"default:desert_stone 10", "default:gold_ingot 8", 27}, + {"default:sapling 1", "default:gold_ingot 1", 7}, + {"default:pick_steel 1", "default:gold_ingot 2", 7}, + {"default:sword_steel 1", "default:gold_ingot 2", 17}, + {"default:shovel_steel 1", "default:gold_ingot 1", 17}, + }, + names = { + "Bob", "Duncan", "Bill", "Tom", "James", "Ian", "Lenny" + } +} + +-- Trader ( same as NPC but with right-click shop ) + +mobs:register_mob("mobs_npc:trader", { + type = "npc", + passive = false, + damage = 3, + attack_type = "dogfight", + attacks_monsters = true, + pathfinding = false, + hp_min = 10, + hp_max = 20, + armor = 100, + collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35}, + visual = "mesh", + mesh = "character.b3d", + textures = { + {"mobs_trader.png"}, -- by Frerin + }, + makes_footstep_sound = true, + sounds = {}, + walk_velocity = 2, + run_velocity = 3, + jump = false, + drops = {}, + water_damage = 0, + lava_damage = 4, + light_damage = 0, + follow = {"default:diamond"}, + view_range = 15, + owner = "", + order = "stand", + fear_height = 3, + animation = { + speed_normal = 30, + speed_run = 30, + stand_start = 0, + stand_end = 79, + walk_start = 168, + walk_end = 187, + run_start = 168, + run_end = 187, + punch_start = 200, + punch_end = 219, + }, + on_rightclick = function(self, clicker) + mobs_trader(self, clicker, entity, mobs.human) + end, +}) + +--This code comes almost exclusively from the trader and inventory of mobf, by Sapier. +--The copyright notice below is from mobf: +------------------------------------------------------------------------------- +-- Mob Framework Mod by Sapier +-- +-- You may copy, use, modify or do nearly anything except removing this +-- copyright notice. +-- And of course you are NOT allow to pretend you have written it. +-- +--! @file inventory.lua +--! @brief component containing mob inventory related functions +--! @copyright Sapier +--! @author Sapier +--! @date 2013-01-02 +-- +--! @defgroup Inventory Inventory subcomponent +--! @brief Component handling mob inventory +--! @ingroup framework_int +--! @{ +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +function mobs.allow_move(inv, from_list, from_index, to_list, to_index, count, player) + + if to_list ~= "selection" + or from_list == "price" + or from_list == "payment" + or from_list == "takeaway" + or from_list == "identifier" then + + return 0 + end + + -- forbid moving split stacks + local old_stack = inv.get_stack(inv, from_list, from_index) + + if count ~= old_stack.get_count(old_stack) then + return 0 + end + + return count +end + +function mobs.allow_put(inv, listname, index, stack, player) + + if listname == "payment" then + return 99 + end + + return 0 +end + +function mobs.allow_take(inv, listname, index, stack, player) + + if listname == "takeaway" + or listname == "payment" then + + return 99 + else + return 0 + end +end + +function mobs.on_put(inv, listname, index, stack) + + if listname == "payment" then + mobs.update_takeaway(inv) + end +end + +function mobs.on_take(inv, listname, count, index, stack, player) + + if listname == "takeaway" then + + local amount = inv:get_stack("payment", 1):get_count() + local price = inv:get_stack("price", 1):get_count() + local thing = inv:get_stack("payment", 1):get_name() + + inv.set_stack(inv,"selection", 1, nil) + inv.set_stack(inv,"price", 1, nil) + inv.set_stack(inv,"takeaway", 1, nil) + inv.set_stack(inv,"payment", 1, thing .. " " .. amount - price) + end + + if listname == "payment" then + + if mobs.check_pay(inv, false) then + + local selection = inv.get_stack(inv, "selection", 1) + + if selection ~= nil then + inv.set_stack(inv,"takeaway", 1, selection) + end + else + inv.set_stack(inv, "takeaway", 1, nil) + end + end +end + +function mobs.update_takeaway(inv) + + if mobs.check_pay(inv,false) then + + local selection = inv.get_stack(inv,"selection", 1) + + if selection ~= nil then + inv.set_stack(inv,"takeaway", 1, selection) + end + else + inv.set_stack(inv,"takeaway", 1, nil) + end +end + +function mobs.check_pay(inv, paynow) + + local now_at_pay = inv.get_stack(inv,"payment", 1) + local count = now_at_pay.get_count(now_at_pay) + local name = now_at_pay.get_name(now_at_pay) + local price = inv.get_stack(inv, "price", 1) + + if price:get_name() == name then + + local price = price:get_count() + + if price > 0 + and price <= count then + + if paynow then + + now_at_pay.take_item(now_at_pay, price) + + inv.set_stack(inv,"payment", 1, now_at_pay) + + return true + else + return true + end + else + if paynow then + inv.set_stack(inv, "payment", 1, nil) + end + end + end + + return false +end + +mobs.trader_inventories = {} + +function mobs.add_goods(entity, race) + + local goods_to_add = nil + + for i = 1, 15 do + + if math.random(0, 100) > race.items[i][3] then + mobs.trader_inventory.set_stack(mobs.trader_inventory, + "goods", i, race.items[i][1]) + end + end +end + +function mobs_trader(self, clicker, entity, race) + + local player = clicker:get_player_name() + + if not self.id then + self.id = (math.random(1, 1000) * math.random(1, 10000)) + .. self.name .. (math.random(1, 1000) ^ 2) + end + + if not self.game_name then + + self.game_name = tostring(race.names[math.random(1, #race.names)]) + self.nametag = S("Trader @1", self.game_name) + + self.object:set_properties({ + nametag = self.nametag, + nametag_color = "#00FF00" + }) + + end + + local unique_entity_id = self.id + local is_inventory = minetest.get_inventory({ + type = "detached", name = unique_entity_id}) + + local move_put_take = { + + allow_move = mobs.allow_move, + allow_put = mobs.allow_put, + allow_take = mobs.allow_take, + + on_move = function(inventory, from_list, from_index, to_list, to_index, count, player) + + if from_list == "goods" + and to_list == "selection" then + + local inv = inventory + local moved = inv.get_stack(inv,to_list, to_index) + local goodname = moved.get_name(moved) + local elements = moved.get_count(moved) + + if elements > count then + + -- remove the surplus parts + inv.set_stack(inv,"selection", 1, + goodname .. " " .. tostring(count)) + + -- the slot we took from is now free + inv.set_stack(inv,"goods",from_index, + goodname .. " " .. tostring(elements - count)) + + -- update the real amount of items in the slot now + elements = count + end + + local good = nil + + for i = 1, #race.items, 1 do + + local stackstring = goodname .." " .. count + + if race.items[i][1] == stackstring then + good = race.items[i] + end + end + + if good ~= nil then + inventory.set_stack(inventory,"price", 1, good[2]) + else + inventory.set_stack(inventory,"price", 1, nil) + end + + mobs.update_takeaway(inv) + + end + end, + + on_put = mobs.on_put, + on_take = mobs.on_take + } + + if is_inventory == nil then + + mobs.trader_inventory = minetest.create_detached_inventory(unique_entity_id, move_put_take) + mobs.trader_inventory.set_size(mobs.trader_inventory,"goods", 15) + mobs.trader_inventory.set_size(mobs.trader_inventory,"takeaway", 1) + mobs.trader_inventory.set_size(mobs.trader_inventory,"selection", 1) + mobs.trader_inventory.set_size(mobs.trader_inventory,"price", 1) + mobs.trader_inventory.set_size(mobs.trader_inventory,"payment", 1) + mobs.add_goods(entity, race) + end + + minetest.chat_send_player(player, S("[NPC] Hello, @2, have a look at my wares.", + self.game_name, player)) + + minetest.show_formspec(player, "trade", "size[8,10;]" + .. default.gui_bg_img + .. default.gui_slots + .. "label[0,0;" .. S("Trader @1's stock:", self.game_name) .. "]" + .. "list[detached:" .. unique_entity_id .. ";goods;.5,.5;3,5;]" + .. "label[4.5,0.5;" .. S("Selection") .. "]" + .. "list[detached:" .. unique_entity_id .. ";selection;4.5,1;5.5,2;]" + .. "label[6,0.5;" .. S("Price") .. "]" + .. "list[detached:" .. unique_entity_id .. ";price;6,1;7,2;]" + .. "label[4.5,3.5;" .. S("Payment") .. "]" + .. "list[detached:" .. unique_entity_id .. ";payment;4.5,4;5.5,5;]" + .. "label[6,3.5;" .. S("Bought items") .. "]" + .. "list[detached:" .. unique_entity_id .. ";takeaway;6,4;7.5,5.5;]" + .. "list[current_player;main;0,6;8,4;]" + ) +end + +mobs:register_egg("mobs_npc:trader", S("Trader"), "default_sandstone.png", 1) + +-- compatibility +mobs:alias_mob("mobs:trader", "mobs_npc:trader") diff --git a/trader_test.lua b/trader_test.lua new file mode 100755 index 0000000..41e734c --- /dev/null +++ b/trader_test.lua @@ -0,0 +1,264 @@ + +if not minetest.get_modpath("shop") then + minetest.register_alias("shop:coin", "default:gold_ingot") +end + +local S = mobs.intllib + +mobs.human = { + items = { + -- item, price, chance + {"default:apple 10", "shop:coin 2", 40}, + {"farming:bread 10", "shop:coin 4", 50}, + {"default:clay 10", "shop:coin 2", 14}, + {"default:brick 10", "shop:coin 4", 17}, + {"default:glass 10", "shop:coin 4", 17}, + {"default:obsidian 10", "shop:coin 15", 50}, + {"default:diamond 1", "default:goldblock 1", 7}, + {"default:goldblock 1", "default:diamond 1", 7}, + {"farming:wheat 10", "shop:coin 2", 17}, + {"default:tree 5", "shop:coin 4", 20}, + {"default:stone 10", "shop:coin 8", 17}, + {"default:desert_stone 10", "shop:coin 8", 27}, + {"default:sapling 1", "shop:coin 1", 7}, + {"default:pick_steel 1", "shop:coin 2", 7}, + {"default:sword_steel 1", "shop:coin 2", 17}, + {"default:shovel_steel 1", "shop:coin 1", 17}, + }, + names = { + "Bob", "Duncan", "Bill", "Tom", "James", "Ian", "Lenny" + } +} + +-- Trader ( same as NPC but with right-click shop ) + +mobs:register_mob("mobs_npc:trader", { + type = "npc", + passive = false, + damage = 3, + attack_type = "dogfight", + attacks_monsters = true, + pathfinding = false, + hp_min = 10, + hp_max = 20, + armor = 100, + collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35}, + visual = "mesh", + mesh = "character.b3d", + textures = { + {"mobs_trader.png"}, -- by Frerin + {"mobs_trader2.png"}, -- re-coloured by amhadinger + {"mobs_trader3.png"}, -- re-coloured by amhadinger + }, + makes_footstep_sound = true, + sounds = {}, + walk_velocity = 2, + run_velocity = 3, + jump = false, + drops = {}, + water_damage = 0, + lava_damage = 4, + light_damage = 0, + follow = {"default:diamond"}, + view_range = 15, + owner = "", + order = "stand", + fear_height = 3, + animation = { + speed_normal = 30, + speed_run = 30, + stand_start = 0, + stand_end = 79, + walk_start = 168, + walk_end = 187, + run_start = 168, + run_end = 187, + punch_start = 200, + punch_end = 219, + }, + on_rightclick = function(self, clicker) + mobs_trader(self, clicker, mobs.human) + end, +}) + +--This code comes almost exclusively from the trader and inventory of mobf, by Sapier. +--The copyright notice below is from mobf: +------------------------------------------------------------------------------- +-- Mob Framework Mod by Sapier +-- +-- You may copy, use, modify or do nearly anything except removing this +-- copyright notice. +-- And of course you are NOT allow to pretend you have written it. +-- +--! @file inventory.lua +--! @brief component containing mob inventory related functions +--! @copyright Sapier +--! @author Sapier +--! @date 2013-01-02 +-- +--! @defgroup Inventory Inventory subcomponent +--! @brief Component handling mob inventory +--! @ingroup framework_int +--! @{ +-- +-- Contact sapier a t gmx net +------------------------------------------------------------------------------- + +-- Modifications Copyright 2016 by James Stevenson + +local trader_inventory = {} + +local function add_goods(race) + local goods_to_add = nil + for i = 1, 16 do + if math.random(0, 100) > race.items[i][3] then + trader_inventory.set_stack(trader_inventory, + "goods", i, race.items[i][1]) + end + end +end + +function mobs_trader(self, clicker, race) + local player = clicker:get_player_name() + + if not self.id then + self.id = (math.random(1, 1000) * math.random(1, 10000)) + .. self.name .. (math.random(1, 1000) ^ 2) + end + + if not self.game_name then + self.game_name = tostring(race.names[math.random(1, #race.names)]) + self.nametag = S("Trader @1", self.game_name) + self.object:set_properties({ + nametag = self.nametag, + nametag_color = "#00FF00" + }) + end + + local unique_entity_id = self.id + local is_inventory = minetest.get_inventory({ + type = "detached", name = unique_entity_id}) + + local move_put_take = { + allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) + if (from_list == "goods" and + to_list == "selection") or + (from_list == "selection" and + to_list == "goods") then + return count + else + return 0 + end + end, + allow_put = function(inv, listname, index, stack, player) + return 0 + end, + allow_take = function(inv, listname, index, stack, player) + return 0 + end, + on_move = function(inv, from_list, from_index, to_list, to_index, count, player) + if from_list == "goods" and + to_list == "selection" then + local moved = inv.get_stack(inv, to_list, to_index) + local goodname = moved.get_name(moved) + local elements = moved.get_count(moved) + if elements > count then + -- Remove the surplus parts + inv.set_stack(inv, "selection", 1, + goodname .. " " .. tostring(count)) + + -- The slot we took from is now free. + inv.set_stack(inv, "goods", from_index, + goodname .. " " .. tostring(elements - count)) + + -- Update the real amount of items in the slot now. + elements = count + end + local good = nil + for i = 1, #race.items, 1 do + local stackstring = goodname .. " " .. count + if race.items[i][1] == stackstring then + good = race.items[i] + end + end + if good ~= nil then + inv.set_stack(inv, "price", 1, good[2]) + else + inv.set_stack(inv, "price", 1, nil) + end + elseif from_list == "selection" and + to_list == "goods" then + inv.set_stack(inv, "price", 1, nil) + end + end, + on_put = function(inv, listname, index, stack, player) + end, + on_take = function(inv, listname, index, stack, player) + end, + } + + if is_inventory == nil then + trader_inventory = minetest.create_detached_inventory(unique_entity_id, move_put_take) + trader_inventory.set_size(trader_inventory, "goods", 16) + trader_inventory.set_size(trader_inventory, "selection", 1) + trader_inventory.set_size(trader_inventory, "price", 1) + add_goods(race) + --print("added stuff") + end + + minetest.chat_send_player(player, S("[NPC] Hello, @2, have a look at my wares.", + self.game_name, player)) + + minetest.show_formspec(player, "mobs_npc:trader", "size[8,9]" .. + default.gui_bg .. + default.gui_bg_img .. + default.gui_slots .. + "list[detached:" .. unique_entity_id .. ";goods;0,0;8,2]" .. + "label[0,3;Selection]" .. + "list[detached:" .. unique_entity_id .. ";selection;2,3;1,1]" .. + "label[4,3;Price]" .. + "list[detached:" .. unique_entity_id .. ";price;6,3;1,1]" .. + "button[4,4;2,1;purchase;Purchase]" .. + "list[current_player;main;0,5;8,1;]" .. + "list[current_player;main;0,6.25;8,3;8]" .. + default.get_hotbar_bg(0, 5)) +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "mobs_npc:trader" then + return + end + + --print(dump(trader_inventory:get_lists())) + + local selection_name = trader_inventory:get_stack("selection", 1):get_name() + local selection_count = trader_inventory:get_stack("selection", 1):get_count() + local selection_string = selection_name .. " " .. tostring(selection_count) + + local price_name = trader_inventory:get_stack("price", 1):get_name() + local price_count = trader_inventory:get_stack("price", 1):get_count() + local price_string = price_name .. " " .. tostring(price_count) + + --print(selection_string .. "\nfor:\n" .. price_string) + + if player:get_inventory():contains_item("main", price_string) then + --print("you got it!") + trader_inventory:set_stack("selection", 1, nil) + trader_inventory:set_stack("price", 1, nil) + + player:get_inventory():remove_item("main", price_string) + local adder = player:get_inventory():add_item("main", selection_string) + if adder then + minetest.add_item(player:getpos(), adder) + end + else + minetest.chat_send_player(player:get_player_name(), + "Not enough credits!") + end + +end) + +mobs:register_egg("mobs_npc:trader", S("Trader"), "default_sandstone.png", 1) + +-- compatibility +mobs:alias_mob("mobs:trader", "mobs_npc:trader")