1--[[
2 This is the virtual sex system created by 𝓜𝓲𝓵𝓵𝓱𝓲𝓸𝓻𝓮 𝓑𝓣
3 For TFS-master 1.5 server
4
5 The virtual sex system is a system that allows players to have sexual relations with other players, this system is fully configurable and can be modified to suit the user.
6
7 -- WARNING --
8 > If you are a programmer and you want to modify the system, you can do it, surely you know what you are doing.
9 > If you're not a programmer, don't modify anything you don't understand, as it may cause system errors.
10]]--
11
12local talkActionWords = "!sex"
13local sexSystem = {
14 types = {"oral", "anal", "vaginal"},
15 intensities = {"mild", "normal", "strong", "extreme", "painful"},
16 conditionTimes = { -- Depending on the intensity the benefits will last longer active.
17 mild = 60000, -- 1 Minute
18 normal = 120000, -- 2 Minutes
19 strong = 180000, -- 3 Minutes
20 extreme = 240000, -- 4 Minutes
21 painful = 300000 -- 5 Minutes
22 },
23
24 childrenId = 6579,
25 childrenMaintance = 10, -- 10% gold from father to mother.
26 childrenChance = 50, -- 50% chance of getting pregnant if the condom fails.
27
28 whiteListStorageBase = 670000,
29 whiteListStorageMax = 100,
30 blackListStorageBase = 670100,
31 blackListStorageMax = 100
32}
33
34local sexPoses = {
35 [PLAYERSEX_FEMALE] = {
36 oral = {"Dressed and seated", "The 69, but on the side", "On knees", "Above his mouth"},
37 anal = {"The doggy", "Side", "Face down", "Riding"},
38 vaginal = {"The back lying", "Romantic landscape", "The amazon", "The missionary"}
39 },
40
41 [PLAYERSEX_MALE] = {
42 oral = {"The lazy", "The supported", "The king of the world", "The automatic"},
43 anal = {"At the border", "Standing", "The immersion", "The little spoon"},
44 vaginal = {"The rabbit", "Legs to shoulder", "The cowgirl", "Forklift truck"}
45 }
46}
47
48local sexCondons = {
49 { type = "Free", security = 0.25, price = 0 },
50 { type = "Latex", security = 0.99, price = 100 },
51 { type = "Polyurethane", security = 0.75, price = 50 },
52 { type = "Animal skin", security = 0.50, price = 10 }
53}
54
55local boostTypes = {
56 regeneration = CONDITION_REGENERATION,
57 skills = CONDITION_ATTRIBUTES,
58 stats = CONDITION_ATTRIBUTES,
59 speed = CONDITION_HASTE,
60 critical = CONDITION_ATTRIBUTES,
61 slowness = CONDITION_PARALYZE
62}
63
64local boostValues = {
65 regeneration = {"HEALTHGAIN", "MANAGAIN"},
66 skills = {"SKILL_AXE", "SKILL_CLUB", "SKILL_SWORD"},
67 stats = {"STAT_MAGICPOINTS"},
68 speed = {"SPEED"},
69 critical = {"SPECIALSKILL_CRITICALHITCHANCE", "SPECIALSKILL_CRITICALHITAMOUNT"},
70 slowness = {"SPEED"}
71}
72
73local defaultValues = {
74 regeneration = {
75 HEALTHTICKS = 1000,
76 MANATICKS = 1000
77 }
78}
79
80local sexBoosts = {
81 [PLAYERSEX_FEMALE] = {
82 oral = {regeneration = 100, skills = 10, stats = 10, speed = 10},
83 anal = {regeneration = 100, skills = 30, stats = 10 , slowness = 10},
84 vaginal = {regeneration = 200, skills = 20, stats = 20, speed = 20}
85 },
86
87 [PLAYERSEX_MALE] = {
88 oral = {regeneration = 100, skills = 10, stats = 10, speed = 10},
89 anal = {regeneration = 100, skills = 30, stats = 10 , critical = 10},
90 vaginal = {regeneration = 200, skills = 20, stats = 20, speed = 20}
91 }
92}
93
94function sexSystem.applyBoosts(player, request)
95 local sexBoost = sexBoosts[player:getSex()][request.type]
96 if not sexBoost then
97 return
98 end
99
100 for type, value in pairs(sexBoost) do
101 local condition = Condition(boostTypes[type])
102 condition:setParameter(CONDITION_PARAM_TICKS, sexSystem.conditionTimes[request.intensity])
103 condition:setParameter(CONDITION_PARAM_SUBID, request.playerId)
104 for _, boost in pairs(boostValues[type] or {}) do
105 condition:setParameter(_G[string.format("CONDITION_PARAM_%s", boost)], value)
106 end
107
108 for boostType, boostValue in pairs(defaultValues[type] or {}) do
109 condition:setParameter(_G[string.format("CONDITION_PARAM_%s", boostType)], boostValue)
110 end
111
112 player:addCondition(condition)
113 end
114
115 addEvent(function (playerId)
116 local player = Player(playerId)
117 if player then
118 player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("You have received a boost for %s seconds.", math.floor(sexSystem.conditionTimes[request.intensity] / 1000)))
119 end
120 end, 3000, player:getId())
121end
122
123local function getIndexByList(player, listName, targetId)
124 targetId = targetId or -1
125 for key = sexSystem[string.format("%sStorageBase", listName)], sexSystem[string.format("%sStorageMax", listName)] - 1 do
126 if player.storage[key] == targetId then
127 return key
128 end
129 end
130end
131
132local function setIndexByList(player, listName, targetId, value)
133 local key = getIndexByList(player, listName, targetId)
134 if key then
135 player.storage[key] = value or -1
136 return true
137 end
138end
139
140local function hasWhiteList(player, targetId) return getIndexByList(player, "whiteList", targetId) end
141local function addWhiteList(player, targetId) return setIndexByList(player, "whiteList", -1, targetId) end
142local function removeWhiteList(player, targetId) return setIndexByList(player, "whiteList", targetId) end
143
144local function hasBlackList(player, targetId) return getIndexByList(player, "blackList", targetId) end
145local function addWhiteList(player, targetId) return setIndexByList(player, "blackList", -1, targetId) end
146local function removeWhiteList(player, targetId) return setIndexByList(player, "blackList", targetId) end
147
148local function createChildren(player, target)
149 local children = Game.createItem(sexSystem.childrenId, 1)
150 if children then
151 children:setCustomAttribute("fatherGuid", player:getGuid())
152 children:setCustomAttribute("motherGuid", target:getGuid())
153 children:setAttribute(ITEM_ATTRIBUTE_NAME, string.format("%s and %s's child", player:getName(), target:getName()))
154 children:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "Use the child to claim your maintenance.")
155 local returnValue = target:addItemEx(children)
156 if returnValue ~= RETURNVALUE_NOERROR then
157 target:sendCancelMessage(returnValue)
158 children:moveTo(target:getPosition())
159 end
160 end
161 return true
162end
163
164local requests = {}
165
166local function resetRequest(playerId, targetId)
167 requests[playerId] = nil
168 if targetId then
169 requests[targetId] = nil
170 end
171end
172
173local talkAction = TalkAction(talkActionWords)
174
175function talkAction.onSay(player, words, param)
176 local pos = player:getPosition()
177 pos:getNextPosition(player:getDirection())
178 local tile = Tile(pos)
179 if not tile then
180 player:sendCancelMessage("There is nothing in front of you.")
181 return false
182 end
183
184 local target = tile:getTopVisibleCreature(player)
185 if not target then
186 player:sendCancelMessage("There is no one in front of you.")
187 return false
188 end
189
190 local targetId = target:getId()
191 if hasBlackList(player, targetId) and not hasWhiteList(player, targetId) then
192 player:sendCancelMessage("You have this player in your blacklist.")
193 return false
194 end
195
196 local request = requests[player:getId()]
197 if request then
198 if type(request) == "table" then
199 sexSystem.handleRequest(player, target, request)
200 return false
201 end
202
203 player:sendCancelMessage("You already have a request.")
204 return false
205 end
206
207 local targetRequest = requests[target:getId()]
208 if targetRequest then
209 player:sendCancelMessage("The player already has a pending request.")
210 return false
211 end
212
213 requests[player:getId()] = target:getId()
214 sexSystem.sendSexTypesWindow(player, target)
215 return false
216end
217
218talkAction:register()
219
220function sexSystem.handleRequest(player, target, request)
221 if player:getId() == request.playerId then
222 player:sendCancelMessage("You already have a pending request.")
223 return false
224 end
225
226 local target = Player(request.playerId)
227 if not target then
228 player:sendCancelMessage("The requesting player is not online.")
229 return false
230 elseif target:getId() ~= request.playerId then
231 player:sendCancelMessage("The player you are looking at was not the one who requested the sex.")
232 return false
233 end
234
235 return sexSystem.sendSexHandleRequestWindow(player, target, request)
236end
237
238function sexSystem.sendSexHandleRequestWindow(player, target, request)
239 local modalWindow = ModalWindow{
240 title = "Sex System - Request",
241 message = string.format("The player %s wants to have sex with you, do you accept?", target:getName())
242 }
243
244 modalWindow:addChoice("No")
245 modalWindow:addChoice("Yes")
246 if not hasBlackList(player, request.playerId) then
247 modalWindow:addChoice("Add to blacklist")
248 end
249
250 if not hasWhiteList(player, request.playerId) then
251 modalWindow:addChoice("Add to whitelist")
252 end
253
254 modalWindow:addButton("Select", function (player, button, choice)
255 local target = Player(request.playerId)
256 if not target then
257 player:sendCancelMessage("The requesting player is not online.")
258 resetRequest(player:getId(), request.playerId)
259 return false
260 end
261
262 if not choice or not table.contains({"Yes", "Add to whitelist"}, choice.text) then
263 if choice.text == "Add to blacklist" then
264 if not hasBlackList(player, request.playerId) then
265 addBlackList(player, request.playerId)
266 player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You have added %s to your blacklist.", target:getName()))
267 end
268 end
269
270 player:sendCancelMessage("You have declined the request.")
271 resetRequest(player:getId(), request.playerId)
272 return false
273 end
274
275 if choice.text == "Add to whitelist" then
276 if not hasWhiteList(player, request.playerId) then
277 addWhiteList(player, request.playerId)
278 player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("You have added %s to your whitelist.", target:getName()))
279 end
280 end
281
282 sexSystem.startSex(target, player, request)
283 resetRequest(player:getId(), request.playerId)
284 end)
285 modalWindow:addButton("Close")
286
287 modalWindow:setDefaultEnterButton("Select")
288 modalWindow:setDefaultEscapeButton("Close")
289 return modalWindow:sendToPlayer(player)
290end
291
292function sexSystem.startSex(player, target, request)
293 local con = request.con
294 if player:getTotalMoney() < con.price then
295 resetRequest(request.playerId, request.targetId)
296 return false
297 end
298
299 player:removeTotalMoney(con.price)
300 player:getPosition():sendMagicEffect(CONST_ME_HEARTS)
301 target:getPosition():sendMagicEffect(CONST_ME_HEARTS)
302 player:sendTextMessage(MESSAGE_LOOT, string.format("You have had {33780|%s} {33952|%s} sex {3035|%s} with {33780|%s}.", request.intensity, request.type, request.pose, target:getName()))
303 target:sendTextMessage(MESSAGE_LOOT, string.format("You have had {33780|%s} {33952|%s} sex {3035|%s} with {33780|%s}.", request.intensity, request.type, request.pose, player:getName()))
304
305 local childrenGenerated = nil
306 if math.random() > con.security then
307 if math.random(1, 100) <= sexSystem.childrenChance then
308 local playerSex = player:getSex()
309 if playerSex ~= target:getSex() then
310 if playerSex == PLAYERSEX_MALE then
311 childrenGenerated = createChildren(player, target)
312 else
313 childrenGenerated = createChildren(target, player)
314 end
315 end
316 end
317 end
318
319 if not childrenGenerated then
320 local fluid = Game.createItem(2016, FLUID_MILK, player:getPosition())
321 if fluid then
322 fluid:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, string.format("%s Fluids", player:getName()))
323 fluid:decay(0)
324 end
325
326 local fluid = Game.createItem(2016, FLUID_MILK, target:getPosition())
327 if fluid then
328 fluid:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, string.format("%s Fluids", target:getName()))
329 fluid:decay(0)
330 end
331 end
332
333 sexSystem.applyBoosts(player, request)
334 sexSystem.applyBoosts(target, request)
335 resetRequest(request.playerId, request.targetId)
336end
337
338function sexSystem.sendSexTypesWindow(player, target)
339 local modalWindow = ModalWindow{
340 title = "Sex System - Type",
341 message = string.format("Select the type of sex you want to have with %s", target:getName())
342 }
343
344 local playerSex = player:getSex()
345 local targetSex = target:getSex()
346
347 for _, sexType in pairs(sexSystem.types) do
348 repeat
349 if sexType == "vaginal" and playerSex == PLAYERSEX_MALE and targetSex == PLAYERSEX_MALE then
350 break
351 end
352
353 modalWindow:addChoice(string.format("%s sex", sexType))
354 until true
355 end
356
357 modalWindow:addButton("Accept", function (player, button, choice)
358 local playerId = player:getId()
359 if not choice or choice.text == "" then
360 resetRequest(playerId)
361 return false
362 end
363
364 local targetId = requests[playerId]
365 if not targetId then
366 resetRequest(playerId)
367 return false
368 end
369
370 local target = Player(targetId)
371 if not target then
372 resetRequest(playerId)
373 return false
374 end
375
376 sexSystem.sendSexIntensitiesWindow(player, target, sexSystem.types[choice.id])
377 return true
378 end)
379 modalWindow:addButton("Cancel", function (player, button, choice)
380 resetRequest(player:getId())
381 end)
382
383 modalWindow:setDefaultEnterButton("Accept")
384 modalWindow:setDefaultEscapeButton("Cancel")
385 return modalWindow:sendToPlayer(player)
386end
387
388function sexSystem.sendSexIntensitiesWindow(player, target, type)
389 local modalWindow = ModalWindow{
390 title = "Sex System - Intensity",
391 message = string.format("Select the intensity with which you want to have %s sex with %s.", type, target:getName())
392 }
393
394 for _, intensity in pairs(sexSystem.intensities) do
395 modalWindow:addChoice(intensity)
396 end
397
398 modalWindow:addButton("Accept", function (player, button, choice)
399 local playerId = player:getId()
400 if not choice or choice.text == "" then
401 resetRequest(playerId)
402 return false
403 end
404
405 local targetId = requests[playerId]
406 if not targetId then
407 resetRequest(playerId)
408 return false
409 end
410
411 local target = Player(targetId)
412 if not target then
413 resetRequest(playerId)
414 return false
415 end
416
417 sexSystem.sendSexPosesWindow(player, target, type, sexSystem.intensities[choice.id])
418 return true
419 end)
420 modalWindow:addButton("Cancel", function (player, button, choice)
421 resetRequest(player:getId())
422 end)
423
424 modalWindow:setDefaultEnterButton("Accept")
425 modalWindow:setDefaultEscapeButton("Cancel")
426 return modalWindow:sendToPlayer(player)
427end
428
429function sexSystem.sendSexPosesWindow(player, target, type, intensity)
430 local modalWindow = ModalWindow{
431 title = "Sex System - Poses",
432 message = string.format("Select the position with which you want to have %s %s sex with %s.", intensity, type, target:getName())
433 }
434
435 local playerId = player:getId()
436 local poses = sexPoses[player:getSex()]
437 if not poses then
438 resetRequest(playerId)
439 return false
440 end
441
442 local poses = poses[type]
443 if not poses then
444 resetRequest(playerId)
445 return false
446 end
447
448 for _, pose in pairs(poses) do
449 modalWindow:addChoice(pose)
450 end
451
452 modalWindow:addButton("Accept", function (player, button, choice)
453 local playerId = player:getId()
454 if not choice or choice.text == "" then
455 resetRequest(playerId)
456 return false
457 end
458
459 local targetId = requests[playerId]
460 if not targetId then
461 resetRequest(playerId)
462 return false
463 end
464
465 local target = Player(targetId)
466 if not target then
467 resetRequest(playerId)
468 return false
469 end
470
471 sexSystem.sendSexCondonsWindow(player, target, type, intensity, poses[choice.id])
472 return true
473 end)
474 modalWindow:addButton("Cancel", function (player, button, choice)
475 resetRequest(player:getId())
476 end)
477
478 modalWindow:setDefaultEnterButton("Accept")
479 modalWindow:setDefaultEscapeButton("Cancel")
480 return modalWindow:sendToPlayer(player)
481end
482
483function sexSystem.sendSexCondonsWindow(player, target, type, intensity, pose)
484 local modalWindow = ModalWindow{
485 title = "Sex System - Condons",
486 message = "Select the condom you want to use:"
487 }
488
489 for _, con in pairs(sexCondons) do
490 modalWindow:addChoice(string.format("%s (%d%%) - %d gold", con.type, con.security * 100, con.price))
491 end
492
493 modalWindow:addButton("Accept", function (player, button, choice)
494 local playerId = player:getId()
495 if not choice or choice.text == "" then
496 resetRequest(playerId)
497 return false
498 end
499
500 local targetId = requests[playerId]
501 if not targetId then
502 resetRequest(playerId)
503 return false
504 end
505
506 local target = Player(targetId)
507 if not target then
508 resetRequest(playerId)
509 return false
510 end
511
512 local con = sexCondons[choice.id]
513 if not con then
514 resetRequest(playerId)
515 return false
516 end
517
518 if player:getTotalMoney() < con.price then
519 player:sendTextMessage(MESSAGE_INFO_DESCR, "You don't have enough money.")
520 resetRequest(playerId)
521 return false
522 end
523
524 sexSystem.createRequest(player, target, type, intensity, pose, con)
525 return true
526 end)
527 modalWindow:addButton("Cancel", function (player, button, choice)
528 resetRequest(player:getId())
529 end)
530
531 modalWindow:setDefaultEnterButton("Accept")
532 modalWindow:setDefaultEscapeButton("Cancel")
533 return modalWindow:sendToPlayer(player)
534end
535
536function sexSystem.createRequest(player, target, type, intensity, pose, con)
537 local request = {
538 playerId = player:getId(),
539 targetId = target:getId(),
540 type = type,
541 intensity = intensity,
542 pose = pose,
543 con = con
544 }
545
546 requests[player:getId()] = request
547 requests[target:getId()] = request
548
549 player:sendTextMessage(MESSAGE_LOOT, string.format("You have requested to have {33780|%s} {33952|%s} sex {3035|%s} with {33780|%s}.\nWait for it to respond to your request.", intensity, type, pose, target:getName()))
550 target:sendTextMessage(MESSAGE_LOOT, string.format("{3038|%s} has asked you to have {33780|%s} {33952|%s} sex {3035|%s}.\nTo handle the request use the command {3035|%s}.", player:getName(), intensity, type, pose, talkActionWords))
551 return true
552end
553
554local action = Action()
555
556function action.onUse(player, item, fromPos, target, toPos, isHotkey)
557 local fatherGuid = item:getCustomAttribute("fatherGuid")
558 local motherGuid = item:getCustomAttribute("motherGuid")
559 if not fatherGuid or not motherGuid then
560 return false
561 end
562
563 local mother = Player(motherGuid)
564 if not mother or motherGuid ~= player:getGuid() then
565 player:sendTextMessage(MESSAGE_INFO_DESCR, "You are not the mother of this child.")
566 return true
567 end
568
569 local father = Player(fatherGuid)
570 if not father then
571 player:sendTextMessage(MESSAGE_INFO_DESCR, "The father of this child is offline.")
572 return true
573 end
574
575 local maintance = math.ceil(father:getTotalMoney() * (sexSystem.childrenMaintance / 100))
576 if player:getTotalMoney() < maintance then
577 player:sendTextMessage(MESSAGE_INFO_DESCR, "The father of this child doesn't have enough money to pay the maintance.")
578 return true
579 end
580
581 father:removeTotalMoney(maintance)
582 player:setBankBalance(player:getBankBalance() + maintance)
583 player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("You have received %d gold from the father of this child.", maintance))
584 child:getPosition():sendMagicEffect(CONST_ME_GIFT_WRAPS)
585 child:remove()
586 return true
587end
588
589action:id(sexSystem.childrenId)
590action:register()