Advertisement
wifiboost

Untitled

Apr 13th, 2023
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.04 KB | None | 0 0
  1. --// Mugger entity
  2.  
  3. AddCSLuaFile()
  4.  
  5. ENT.Base = "base_nextbot"
  6. ENT.AdminSpawnable = true
  7. ENT.Spawnable = false
  8.  
  9. function ENT:Initialize()
  10. -- Start position which will get overridden later
  11. self.startPos = Vector( 0, 0, 0 )
  12.  
  13. -- Random model from the config table
  14. self:SetModel( mugger.config.models[math.random(#mugger.config.models)] )
  15.  
  16. -- Give him a name
  17. self:SetNWString("mugger_name", table.Random( mugger.config.names ) )
  18.  
  19. -- Gotta have a knife for looks
  20. self.weapon = self:createKnife()
  21.  
  22. -- Values
  23. self.curSearchTime = 0
  24. self.stuckCount = 0
  25. self.cooldown = false
  26. self.targetHit = false
  27. self.inUse = false
  28. self.usingPlayer = nil
  29. self.funder = nil
  30.  
  31. -- Spawn protection
  32. self:SetHealth(5000)
  33. end
  34.  
  35. function ENT:createKnife()
  36. if CLIENT then return end
  37.  
  38. -- Get right hand attachment
  39. local att = "anim_attachment_RH"
  40. local shootpos = self:GetAttachment( self:LookupAttachment(att) )
  41.  
  42. -- Create the knife
  43.  
  44. local wep = ents.Create("weapon_mug_knife")
  45. wep:SetOwner( self )
  46. wep:SetPos( shootpos.Pos )
  47. wep:SetHoldType( "knife" )
  48. wep:Spawn()
  49.  
  50. -- No collisions
  51. wep:SetSolid( SOLID_NONE )
  52. wep:SetParent( self )
  53.  
  54. -- Attach it to the bot
  55. wep:Fire( "setparentattachment", "anim_attachment_RH" )
  56. wep:AddEffects( EF_BONEMERGE )
  57. wep:SetAngles( self:GetForward():Angle() )
  58.  
  59. return wep
  60. end
  61.  
  62. --// Helper function for debugging
  63. function ENT:setStatus( text )
  64. if mugger.config.debug then
  65. print("[Mugger]: "..text)
  66. self:SetNWString( "mugger_status", text )
  67. end
  68. end
  69.  
  70. --// Helper function for debugging
  71. function ENT:getStatus()
  72. return self:GetNWString( "mugger_status", "nil" )
  73. end
  74.  
  75. --// Set a new target for the mugger to attack
  76. function ENT:setTarget( ent )
  77. self.targetHit = false
  78. self.target = ent
  79. end
  80.  
  81. --// Returns the mugger's target
  82. function ENT:getTarget()
  83. return self.target
  84. end
  85.  
  86. --// Does this mugger have a target?
  87. function ENT:hasTarget()
  88. if IsValid( self:getTarget() ) then
  89. if self.target:IsPlayer() then
  90. return self.target:Alive()
  91. end
  92. end
  93. return false
  94. end
  95.  
  96. function ENT:fullReset()
  97. self.curSearchTime = 0
  98. self.stuckCount = 0
  99. self.cooldown = false
  100. self.targetHit = false
  101. self.inUse = false
  102. self.usingPlayer = nil
  103. self.funder = nil
  104. end
  105.  
  106. function ENT:completedMugging()
  107. self.funder = nil
  108. self.targetHit = false
  109. self:setTarget( nil )
  110. end
  111.  
  112. local lastNotif = 0
  113. function ENT:Use( activator, ply, type, val )
  114. if CLIENT then return end
  115.  
  116. -- Prevent the use function from being called more than once by the same player
  117. if self.usingPlayer == ply then return end
  118.  
  119. -- Check that their job can hire the mugger
  120. local canUse = true
  121. if #mugger.config.useableJobs > 0 then
  122. canUse = false
  123. for _, job in pairs( mugger.config.useableJobs ) do
  124. if job:lower() == team.GetName( ply:Team() ):lower() then
  125. canUse = true
  126. break
  127. end
  128. end
  129. end
  130.  
  131. if not canUse then
  132. if CurTime() > lastNotif then
  133. net.Start("mug_notify")
  134. net.WriteString("Your job cannot hire the mugger!")
  135. net.Send( ply )
  136. lastNotif = CurTime() + 1
  137. end
  138. return
  139. end
  140.  
  141. -- Check that their usergroup can hire the mugger
  142. canUse = true
  143. if #mugger.config.useableUsergroups > 0 then
  144. canUse = false
  145. for _, job in pairs( mugger.config.useableUsergroups ) do
  146. if job:lower() == ply:GetUserGroup():lower() then
  147. canUse = true
  148. break
  149. end
  150. end
  151. end
  152.  
  153. if not canUse then
  154. if CurTime() > lastNotif then
  155. net.Start("mug_notify")
  156. net.WriteString("Your usergroup cannot hire the mugger!")
  157. net.Send( ply )
  158. lastNotif = CurTime() + 1
  159. end
  160. return
  161. end
  162.  
  163. -- Make sure the mugger is not in the middle of something important
  164. if not self.inUse and not self:hasTarget() and not IsValid( self.funder ) and not self.cooldown and not self.usingPlayer then
  165. mugger.log( ply:Nick().." is now interacting with mugger: "..self:GetNWString("mugger_name","") )
  166.  
  167. net.Start("mug_panel")
  168. --net.WriteString( self:GetNWString("mugger_name",""), 8 )
  169. net.WriteVector( self:GetPos() )
  170. net.Send( ply )
  171.  
  172. -- Rotate the mugger to face its user
  173. local ratio = 0
  174. timer.Create("mugger_turn", 0.1, 10, function()
  175. ratio = ratio + 0.1
  176.  
  177. local ang = self:GetAngles()
  178. local dif = ( ply:GetPos() - self:GetPos() ):Angle()
  179.  
  180. local y = Lerp( ratio, dif.y, ang.y )
  181.  
  182. self:SetAngles( Angle( ang.p, y, ang.r ) )
  183. end)
  184.  
  185. ply.usingMugger = self
  186. self.usingPlayer = ply
  187. self.inUse = true
  188. else
  189. if CurTime() > lastNotif then -- Prevent spam
  190. net.Start("mug_notify")
  191. net.WriteString("This mugger is busy at the moment")
  192. net.Send( ply )
  193. lastNotif = CurTime() + 1
  194.  
  195. local debug = string.format( "Mugger is busy! InUse: %s. Target: %s. Valid: %s. Cooldown: %s. Using: %s",
  196. self.inUse, self:hasTarget(), IsValid( self.funder ), self.cooldown, self.usingPlayer)
  197.  
  198. -- Print the details into console so they can be checked
  199. ply:PrintMessage( HUD_PRINTCONSOLE, debug.."\tOther:"..tostring(self.funder).." "..tostring(self.usingPlayer))
  200.  
  201. if mugger.config.debug then
  202. print(debug)
  203. end
  204. end
  205. end
  206. end
  207.  
  208. function ENT:Think()
  209. if CLIENT then return end
  210.  
  211. if not IsValid( self.weapon ) then
  212. self.weapon = self:createKnife()
  213. end
  214.  
  215. -- Make sure our using player is valid
  216. if self.inUse then
  217. self:setStatus("In use by "..tostring(self.usingPlayer))
  218.  
  219. -- If something happens to our user, disregard him
  220. if not IsValid(self.usingPlayer) or not self.usingPlayer:Alive() or self.usingPlayer:isArrested() then
  221. self.usingPlayer = nil
  222. self.funder = nil
  223. self.inUse = false
  224.  
  225. if self.usingPlayer then
  226. self.usingPlayer.usingMugger = nil
  227. end
  228. end
  229. end
  230.  
  231. self.nextDoorCheck = self.nextDoorCheck or 0
  232.  
  233. -- Check periodically for doors and other interactable objects when we are out running
  234. if CurTime() > self.nextDoorCheck and self:GetPos():Distance( self.startPos ) > 20 then
  235. -- Find all doors in front of the mugger
  236. local tr = util.TraceLine( {
  237. start = self:EyePos(),
  238. endpos = self:EyePos() + self:EyeAngles():Forward() * 200,
  239. filter = self
  240. } )
  241.  
  242. local delay = 0.5
  243.  
  244. local ent = tr.Entity
  245.  
  246. if IsValid(ent) and self:GetPos():Distance( ent:GetPos()) < 100 then -- Make sure its valid and we are close to it
  247. local isDoor, isSliding = mugger.isDoor( ent )
  248. if isDoor and not ent:isDoorOpen() and not ent:isDoorOpening() then -- Open closed doors
  249. local val = ent:GetKeyValues()
  250. local oldDir = val["opendir"] -- Get the old open direction
  251.  
  252. -- Override the open direction because doors are stupid and open in the muggers face
  253. local fwd = ent:GetForward()
  254. if fwd.p == 1 then
  255. ent:SetKeyValue( "opendir", "0" )
  256. elseif fwd.y == 1 then
  257. ent:SetKeyValue( "opendir", "1" )
  258. end
  259.  
  260. -- Open it
  261. ent:Fire("open")
  262. self.openedDoor = true
  263.  
  264. self:setStatus("Opened door")
  265.  
  266. -- Restore open direction
  267. ent:SetKeyValue( "opendir", tostring(oldDir) )
  268. elseif string.find(ent:GetClass(), "func_breakable") then -- Shatter glass
  269. ent:Fire("break")
  270. end
  271. elseif self:GetPos():Distance( tr.HitPos ) > 200 then -- Nothing close
  272. delay = 1.5 -- So we don't need to trace as often
  273. end
  274.  
  275. self.nextDoorCheck = CurTime() + delay
  276. end
  277.  
  278. if self:getStatus():lower() == "returning" then
  279. if mugger.config.debug then
  280. print("Mugger is "..math.floor(self:GetPos():Distance( self.startPos )).." units away from their start position")
  281. end
  282. end
  283.  
  284. if self:hasTarget() then
  285. -- Remove spawn protection
  286. if self:Health() > mugger.config.health then
  287. self:SetHealth( Lerp( 0.1, self:Health(), mugger.config.health ) )
  288. end
  289.  
  290. -- Increment the search time
  291. self.curSearchTime = self.curSearchTime + engine.TickInterval()
  292.  
  293. -- Its been too long since we've seen the mugger
  294. if self.curSearchTime > mugger.config.maxSearchTime then
  295. self:setStatus("Exceeded max search time")
  296. if IsValid( self.funder ) then
  297. net.Start("mug_notify")
  298. net.WriteString("Your mugger has given up trying to find the your target.")
  299. net.Send( self.funder )
  300.  
  301. if mugger.config.refunds then
  302. net.Start("mug_notify")
  303. net.WriteString("You were refunded $"..mugger.config.hireCost)
  304. net.Send( self.funder )
  305.  
  306. self.funder:addMoney( mugger.config.hireCost )
  307. end
  308. end
  309.  
  310. self.funder = nil
  311.  
  312. self.targetHit = false
  313. self:setTarget( nil )
  314. self.curSearchTime = 0
  315. return
  316. end
  317.  
  318. local pos = self:GetPos()
  319. local tPos = self:getTarget():GetPos()
  320.  
  321. -- If we are close to the target and they are on a ledge, jump
  322. if math.abs(pos.x - tPos.x) < 100 and math.abs(pos.y - tPos.y) < 100 then
  323. if tPos.z > pos.z and tPos.z - pos.z > 20 and tPos.z - pos.z < 70 then
  324. self:setStatus("Jumping to reduce gap of "..tostring(tPos.z - pos.z) )
  325. self.loco:Jump()
  326. end
  327. end
  328.  
  329. -- We are close enough to mug them
  330. if math.abs(pos.x - tPos.x) < 35 and math.abs(pos.y - tPos.y) < 50 and tPos.z - pos.z < 70 then
  331. self:setStatus("In range of target, mugging")
  332.  
  333. local ply = self:getTarget()
  334. self:setTarget( nil )
  335. self.targetHit = true
  336.  
  337. self.weapon:EmitSound( "Weapon_Knife.Hit" )
  338. self:EmitSound( "vo/coast/odessa/male01/nlo_cheer0"..math.random(1,4)..".wav" ) -- Victory screech!
  339.  
  340. -- Steal a variable amount of money from our victim and make sure we don't steal more money than they have
  341. local steal
  342. if mugger.config.maxMoneyStolen > ply:getDarkRPVar("money") then
  343. steal = math.random( mugger.config.minMoneyStolen, ply:getDarkRPVar("money") )
  344. else
  345. steal = math.random( mugger.config.minMoneyStolen, mugger.config.maxMoneyStolen )
  346. end
  347.  
  348. -- Transaction
  349. self.stolenMoney = steal
  350. ply:addMoney( -steal )
  351.  
  352. net.Start("mug_notify")
  353. net.WriteString("You have been mugged for $"..steal.."! Don't let him get away!!")
  354. net.Send( ply )
  355.  
  356. -- Take damage
  357. if mugger.config.takeDamage then
  358. ply:TakeDamage( math.random(mugger.config.minDamage, mugger.config.maxDamage), self, self.weapon )
  359. end
  360.  
  361. -- Check to see if they survived the stab
  362. if ply:Alive() then
  363. -- Create our fake knocked down player
  364. ply:CreateRagdoll()
  365. local rag = ply:GetRagdollEntity()
  366. rag:SetNWBool( "mug_ragdoll", true )
  367. ply:SetNoDraw( true )
  368. ply:Freeze( true )
  369. ply:SetNWBool( "mug_ragdoll", true )
  370.  
  371. -- Reset it all
  372. timer.Simple(mugger.config.downTime, function()
  373. ply:SetNWBool( "mug_ragdoll", false )
  374. ply:SetNoDraw( false )
  375. ply:Freeze( false )
  376.  
  377. if IsValid( rag ) then
  378. rag:Remove()
  379. end
  380. end)
  381. end
  382.  
  383. local chance = math.random(50)
  384. if chance == 1 then
  385. self:Ignite( 5 )
  386. end
  387. end
  388. else
  389. self.curSearchTime = 0
  390. end
  391. end
  392.  
  393. function ENT:RunBehaviour()
  394.  
  395. while true do
  396. if self:hasTarget() then
  397. self:setStatus("Chasing target")
  398. self.loco:FaceTowards(self:getTarget():GetPos())
  399. self:StartActivity( ACT_RUN )
  400. self.loco:SetDesiredSpeed( mugger.config.chaseSpeed )
  401. self.loco:SetAcceleration( mugger.config.chaseAcceleration )
  402. local result = self:chaseEnemy()
  403. self.loco:SetAcceleration( 400 )
  404. self:StartActivity( ACT_IDLE )
  405.  
  406. -- Could not reach the target so we give up and head back
  407. if result == "stuck" or result == "failed" then
  408. self:setStatus("Failed to reach target, giving up")
  409.  
  410. self.targetHit = false
  411. self:setTarget( nil )
  412. end
  413. else
  414. -- Mugger is making his way back home
  415. if self:GetPos():Distance( self.startPos ) > 40 then
  416. self:setStatus("Returning")
  417. self:StartActivity( ACT_RUN )
  418. self.loco:SetDesiredSpeed( mugger.config.fleeSpeed )
  419. self.loco:SetAcceleration( mugger.config.fleeAcceleration )
  420. local result = self:MoveToPos( self.startPos )
  421.  
  422. -- Our home is blocked, lets wander a bit to try to get unstuck
  423. if result == "stuck" or result == "failed" then
  424. self:setStatus("Attempting to get unstuck")
  425. local pos = self:GetPos()
  426. self:MoveToPos( Vector( pos.x + math.random(-100,100), pos.y + math.random(-100,100), pos.z ) )
  427. end
  428. else
  429. -- Mugger was hired
  430. if IsValid( self.funder ) then
  431. if self.targetHit then
  432. net.Start("mug_notify")
  433. net.WriteString("Your mugger successfully mugged the target and got you $"..self.stolenMoney.."!")
  434. net.Send( self.funder )
  435.  
  436. self.funder:addMoney( self.stolenMoney )
  437. else
  438. net.Start("mug_notify")
  439. net.WriteString("Your mugger was unable to reach the target")
  440. net.Send( self.funder )
  441.  
  442. -- Are we nice enough to refund the player?
  443. if mugger.config.refunds then
  444. net.Start("mug_notify")
  445. net.WriteString("You were refunded $"..mugger.config.hireCost)
  446. net.Send( self.funder )
  447.  
  448. self.funder:addMoney( mugger.config.hireCost )
  449. end
  450. end
  451.  
  452. self:completedMugging()
  453. self:setStatus("Cooldown")
  454.  
  455. -- Cooldown after being hired
  456. self.cooldown = true
  457. timer.Simple( mugger.config.cooldownTime, function()
  458. self.cooldown = false
  459. self.funder = nil
  460. end)
  461. else
  462. -- We are resting, reset variables
  463. self.stolenMoney = 0
  464. self:RemoveAllDecals()
  465. self:SetHealth( 5000 )
  466. end
  467. end
  468. end
  469. -- Stop the mugger from wandering when somebody tries to use him
  470. if self.inUse then
  471. local result = self:MoveToPos( self:GetPos() )
  472. self:StartActivity( ACT_IDLE )
  473. end
  474.  
  475. if self.cooldown == true then -- Chill out for a while
  476. self:setStatus("Cooling")
  477. self:StartActivity( ACT_IDLE )
  478. elseif mugger.config.wanderWhenIdle then -- Wander about because the mugger is idle
  479. --local result = self:MoveToPos( self:GetPos() )
  480. self:StartActivity( ACT_IDLE )
  481. elseif mugger.config.wanderAlongPath then -- Follow our predetermined path because we have no jobs
  482. -- local result = self:MoveToPos( self:GetPos() )
  483. self:StartActivity( ACT_IDLE )
  484. else -- Otherwise we just stand still
  485. -- local result = self:MoveToPos( self:GetPos() )
  486. self:StartActivity( ACT_IDLE )
  487. end
  488.  
  489. coroutine.wait( .5 )
  490. end
  491. end
  492.  
  493. --// Default MoveToPos function edited to fail when the mugger is in use
  494. function ENT:MoveToPos( pos, options )
  495.  
  496. local options = options or {}
  497.  
  498. local path = Path( "Follow" )
  499. path:SetMinLookAheadDistance( options.lookahead or 300 )
  500. path:SetGoalTolerance( options.tolerance or 20 )
  501. path:Compute( self, pos )
  502.  
  503. if ( !path:IsValid() ) then return "failed" end
  504.  
  505. while ( path:IsValid() and not self.inUse ) do
  506.  
  507. path:Update( self )
  508.  
  509. if ( options.draw ) then
  510. path:Draw()
  511. end
  512.  
  513. if ( self.loco:IsStuck() ) then
  514. self:HandleStuck();
  515. return "stuck"
  516. end
  517.  
  518. if ( options.maxage ) then
  519. if ( path:GetAge() > options.maxage ) then return "timeout" end
  520. end
  521.  
  522. if ( options.repath ) then
  523. if ( path:GetAge() > options.repath ) then path:Compute( self, pos ) end
  524. end
  525. coroutine.yield()
  526. end
  527. return "ok"
  528. end
  529.  
  530. --// Garry's Mod wiki chase enemy function (modified) because there is no need to reinvent the wheel
  531. function ENT:chaseEnemy( options )
  532.  
  533. local options = options or {}
  534.  
  535. local path = Path( "Chase" )
  536. path:SetMinLookAheadDistance( options.lookahead or 300 )
  537. path:SetGoalTolerance( options.tolerance or 20 )
  538. path:Chase( self, self:getTarget() )
  539. --path:Compute( self, self:getTarget():GetPos() )
  540.  
  541. if ( !path:IsValid() ) then return "failed" end
  542.  
  543. while ( path:IsValid() and self:hasTarget() ) do
  544.  
  545. -- Fixes them from being stuck in a permanent jump position
  546. if self.loco:IsClimbingOrJumping() then
  547. self:setStatus("Fix jump")
  548. self:StartActivity( ACT_RUN )
  549. end
  550.  
  551. if ( path:GetAge() > 0.1 ) then
  552. path:Compute(self, self:getTarget():GetPos())
  553. end
  554. path:Update( self )
  555.  
  556. if ( options.draw ) then path:Draw() end
  557.  
  558. if ( self.loco:IsStuck() or self.openedDoor ) then
  559. self:setStatus("Stuck")
  560.  
  561. self:HandleStuck()
  562.  
  563. local pos = self:GetPos()
  564.  
  565. if self.openedDoor then
  566. -- Opened a door, so we need to back up so it can open
  567. self:MoveToPos( pos - (self:GetForward()*100) )
  568. else
  569. -- Just generally stuck, move around a little
  570. local sign = math.random(-1,1)
  571. self:MoveToPos( pos - (self:GetForward()*100) + (self:GetRight() * 100 * sign) )
  572. end
  573.  
  574. if not self.openedDoor then -- Don't count this as a stuck if it was manual
  575. self.stuckCount = self.stuckCount + 1
  576. end
  577. self.openedDoor = false
  578.  
  579. -- Try try again until we finally give up
  580. if self.stuckCount > 3 and not self.openedDoor then
  581. self.stuckCount = 0
  582. return "stuck"
  583. end
  584. end
  585. coroutine.yield()
  586. end
  587. return "ok"
  588. end
  589.  
  590. --// Fire, fire, fire!
  591. function ENT:OnIgnite()
  592. self:setTarget( nil )
  593. end
  594.  
  595. --// Damage handler
  596. function ENT:OnInjured( dmginfo )
  597. if self:Health() <= mugger.config.health then -- Make sure we are actually on a job
  598. local wep = dmginfo:GetAttacker().GetActiveWeapon and dmginfo:GetAttacker():GetActiveWeapon() or "[NULL ENTITY]"
  599. local dmg = dmginfo:GetDamage() or 0
  600.  
  601. -- CW2.0 doesn't like Nextbots so we need to manually fetch and deal the damage ourselves
  602. if dmg == 0 then
  603. if type(wep) != "string" then
  604. dmg = wep.Damage or 15
  605. else
  606. mugger.log(string.format( "Mugger (%s) was damaged by a null entity"))
  607. return
  608. end
  609. end
  610.  
  611. self:SetHealth( self:Health() - dmg )
  612.  
  613. mugger.log(string.format( "Mugger (%s) was damaged for (%s) damage by a (%s)",
  614. self:GetNWString("mugger_name",""), dmg, wep))
  615. end
  616. end
  617.  
  618. --// Death handler
  619. function ENT:OnKilled( dmginfo )
  620. -- Cache this because it will be nil once we remove the mugger
  621. local spawnPos = self.startPos
  622.  
  623. -- Standard death stuff
  624. hook.Call( "OnNPCKilled", GAMEMODE, self, dmginfo:GetAttacker(), dmginfo:GetInflictor() )
  625.  
  626. -- Create our own ragdoll
  627. local rag = ents.Create("prop_ragdoll")
  628. rag:SetPos( self:GetPos() )
  629. rag:SetAngles( self:GetAngles() )
  630. rag:SetModel( self:GetModel() )
  631. rag:SetSkin( self:GetSkin() )
  632. rag:SetNWBool("mug_ragdoll", true)
  633. self:Remove()
  634. rag:Spawn()
  635.  
  636. -- Alert other players
  637. net.Start("mug_notify")
  638. net.WriteString("The mugger has been killed! Another will take his place shortly")
  639. net.Broadcast()
  640.  
  641. -- Alert the hirer
  642. if self.funder then
  643. net.Start("mug_notify")
  644. net.WriteString("Your mugger was killed while in action! Your money was lost") -- No refunds if the mugger died
  645. net.Send( self.funder )
  646.  
  647. -- Drop a bag o' money
  648. local pos = self:GetPos()
  649. DarkRP.createMoneyBag( Vector( pos.x, pos.y, pos.z + 20 ), (self.stolenMoney or 0) + mugger.config.hireCost)
  650. end
  651.  
  652. self.funder = nil
  653. self.targetHit = false
  654. self:setTarget( nil )
  655.  
  656. -- Get rid of the knife
  657. if IsValid( self.weapon ) then
  658. self.weapon:Remove()
  659. end
  660.  
  661. -- Simple body despawn
  662. timer.Simple(5, function()
  663. if IsValid( rag ) then
  664. rag:Remove()
  665. end
  666. end)
  667.  
  668. if mugger.config.shouldRespawn then
  669. -- Respawn a new mugger
  670. timer.Simple(mugger.config.respawnDelay, function()
  671. mugger.spawnNew( spawnPos )
  672. end)
  673. end
  674. end
  675.  
  676. list.Set( "NPC", "mugger", {
  677. Name = "Mugger",
  678. Class = "mugger",
  679. Category = "Nextbot"
  680. })
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement