Update nl.moon

Update nl.moon

-- Copyright (C) 2017-2019 DBot

-- 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.

-- editor stuffs

gui.ppm2.dxlevel.not_supported = 'Uw DirectX™ level is te laag. Ten minste 9.0 is vereist. Als je 8.1 voor framerate gebruikt, heb je ofwel de oudste videokaart of slechte drivers.\nOmdat framerate in gmod alleen laag kan zijn vanwege andere addons die zinloze hoge CPU belasting creëren.\nJa, dit bericht zal meerdere malen verschijnen om je te ergeren. Omdat WAT DE NEUK WAAROM ZOU JE MELDINGEN GEVEN OVER ONTBREKENDE TEXTUREN???'
gui.ppm2.dxlevel.toolow = 'DirectX™ level is te laag voor PPM/2'

gui.ppm2.editor.eyes.separate = 'Gebruik gescheiden instellingen voor de ogen'
gui.ppm2.editor.eyes.url = 'Oog URL textuur'
gui.ppm2.editor.eyes.url_desc = 'Bij gebruik van de oog-URL-structuur; De volgende opties hebben geen effect'

gui.ppm2.editor.eyes.lightwarp_desc = 'Lightwarp heeft alleen effect op EyeRefract ogen'
gui.ppm2.editor.eyes.lightwarp = "Lightwarp"
gui.ppm2.editor.eyes.desc1 = "Lightwarp textuur URL invoer\nIt moet 256x16 zijn!"
gui.ppm2.editor.eyes.desc2 = "Glanssterkte\nDeze parameters passen de sterkte van real time reflecties op het oog aan\nOm veranderingen te zien, zet ppm2_cl_reflections convar op 1\nAndere spelers zouden alleen reflecties zien met ppm2_cl_reflecties ingesteld op 1\n0 - is gematteerd; 1 - is gespiegeld"

for _, {tprefix, prefix} in ipairs {{'def', ''}, {'left', 'Left '}, {'right', 'Right '}}
	gui.ppm2.editor.eyes[tprefix].lightwarp.shader = "#{prefix}Gebruik EyeRefract shader"
	gui.ppm2.editor.eyes[tprefix].lightwarp.cornera = "#{prefix}Gebruik Eye Cornera diffuus"
	gui.ppm2.editor.eyes[tprefix].lightwarp.glossiness = "#{prefix}Glans"

	gui.ppm2.editor.eyes[tprefix].type = "#{prefix}Oogtype"
	gui.ppm2.editor.eyes[tprefix].reflection_type = "#{prefix}Type oogreflectie"
	gui.ppm2.editor.eyes[tprefix].lines = "#{prefix}Ooglijnen"
	gui.ppm2.editor.eyes[tprefix].derp = "#{prefix}Derp ogen"
	gui.ppm2.editor.eyes[tprefix].derp_strength = "#{prefix}Derp oog sterkte"
	gui.ppm2.editor.eyes[tprefix].iris_size = "#{prefix}Ooggrootte"

	gui.ppm2.editor.eyes[tprefix].points_inside = "#{prefix}Ooglijntjes binnenin"
	gui.ppm2.editor.eyes[tprefix].width = "#{prefix}Oogbreedte"
	gui.ppm2.editor.eyes[tprefix].height = "#{prefix}Ooghoogte"

	gui.ppm2.editor.eyes[tprefix].pupil.width = "#{prefix}Pupil breedte"
	gui.ppm2.editor.eyes[tprefix].pupil.height = "#{prefix}Pupil hoogte"
	gui.ppm2.editor.eyes[tprefix].pupil.size = "#{prefix}Pupil grootte"

	gui.ppm2.editor.eyes[tprefix].pupil.shift_x = "#{prefix}Pupil verschuiving X"
	gui.ppm2.editor.eyes[tprefix].pupil.shift_y = "#{prefix}Pupil verschuiving Y"
	gui.ppm2.editor.eyes[tprefix].pupil.rotation = "#{prefix}Oog rotatie"

	gui.ppm2.editor.eyes[tprefix].background = "#{prefix}Oog achtergrond"
	gui.ppm2.editor.eyes[tprefix].pupil_size = "#{prefix}Pupil"
	gui.ppm2.editor.eyes[tprefix].top_iris = "#{prefix}Bovenste oog iris"
	gui.ppm2.editor.eyes[tprefix].bottom_iris = "#{prefix}Onderste oog iris"
	gui.ppm2.editor.eyes[tprefix].line1 = "#{prefix}Oog lijn 1"
	gui.ppm2.editor.eyes[tprefix].line2 = "#{prefix}Oog lijn 2"
	gui.ppm2.editor.eyes[tprefix].reflection = "#{prefix}Oogreflectie effect"
	gui.ppm2.editor.eyes[tprefix].effect = "#{prefix}Oog effect"

gui.ppm2.editor.generic.title = 'PPM/2 Pony Editor'
gui.ppm2.editor.generic.title_file = '%q - PPM/2 Pony Editor'
gui.ppm2.editor.generic.title_file_unsaved = '%q* - PPM/2 Pony Editor (niet opgeslagen wijzigingen!)'

gui.ppm2.editor.generic.yes = 'Yas!'
gui.ppm2.editor.generic.no = 'Noh!'
gui.ppm2.editor.generic.ohno = 'Onoh!'
gui.ppm2.editor.generic.okay = 'Okai ;w;'
gui.ppm2.editor.generic.datavalue = '%s\nGegevenswaarde: %q'
gui.ppm2.editor.generic.url = '%s\n\nLink gaat naar: %s'
gui.ppm2.editor.generic.url_field = 'URL veld'
gui.ppm2.editor.generic.spoiler = 'Mysterieuze spoiler'

gui.ppm2.editor.generic.restart.needed = 'Editor moet opnieuw worden opgestart'
gui.ppm2.editor.generic.restart.text = 'U moet de editor herstarten voor het toepassen van wijzigingen.\nNu herstarten?\nNiet opgeslagen gegevens gaan verloren!'
gui.ppm2.editor.generic.fullbright = 'Vol helder'
gui.ppm2.editor.generic.wtf = 'Om de een of andere reden heeft uw speler geen NetworkedPonyData - Niets te bewerken!\nProbeer ppm2_reload in je console en probeer de editor opnieuw te openen'

gui.ppm2.editor.io.random = 'Willekeurig!'
gui.ppm2.editor.io.newfile.title = 'Nieuw Bestand'
gui.ppm2.editor.io.newfile.confirm = 'Wilt u echt een nieuw bestand aanmaken?'
gui.ppm2.editor.io.newfile.toptext = 'Reset'
gui.ppm2.editor.io.delete.confirm = 'Wilt u dat bestand echt verwijderen?\nHet zal voor altijd verdwenen zijn!\n(een lange tijd!)'
gui.ppm2.editor.io.delete.title = 'Echt verwijderen?'

gui.ppm2.editor.io.filename = 'Bestandsnaam'
gui.ppm2.editor.io.hint = 'Bestand openen door te dubbelklikken'
gui.ppm2.editor.io.reload = 'Bestandslijst opnieuw laden'
gui.ppm2.editor.io.failed = 'Heeft niet kunnen importeren.'

gui.ppm2.editor.io.warn.oldfile = '!!! Het kan wel of niet werken. U zult worden geplet.'
gui.ppm2.editor.io.warn.text = "Op dit moment heeft u uw wijzigingen niet aangegeven.\nWilt u echt een ander bestand openen?"
gui.ppm2.editor.io.warn.header = 'Niet opgeslagen wijzigingen!'
gui.ppm2.editor.io.save.button = 'Opslaan'
gui.ppm2.editor.io.save.text = 'Voer de bestandsnaam in zonder ppm2/ en .dat\nTip: om op te slaan als autoload, type "_current" (zonder citaten)'
gui.ppm2.editor.io.wear = 'Wijzigingen aanbrengen (dragen)'

gui.ppm2.editor.seq.standing = 'Staand'
gui.ppm2.editor.seq.move = 'In beweging'
gui.ppm2.editor.seq.walk = 'Wandelen'
gui.ppm2.editor.seq.sit = 'Zitten'
gui.ppm2.editor.seq.swim = 'Zwemmen'
gui.ppm2.editor.seq.run = 'Rennen'
gui.ppm2.editor.seq.duckwalk = 'Wandel als een eend'
gui.ppm2.editor.seq.duck = 'Doe als een eend'
gui.ppm2.editor.seq.jump = 'Springen'

gui.ppm2.editor.misc.race = 'Ras'
gui.ppm2.editor.misc.weight = 'Gewicht'
gui.ppm2.editor.misc.size = 'Pony grootte'
gui.ppm2.editor.misc.hide_weapons = 'Moet wapens verbergen'
gui.ppm2.editor.misc.chest = 'Mannelijke borstkas'
gui.ppm2.editor.misc.gender = 'Geslacht'
gui.ppm2.editor.misc.wings = 'Vleugels Type'
gui.ppm2.editor.misc.flexes = 'Flexibele besturing'
gui.ppm2.editor.misc.no_flexes2 = 'Geen flexibiliteit op het nieuwe model'
gui.ppm2.editor.misc.no_flexes_desc = 'U kunt elke flexstatuscontroller apart uitschakelen\nDeze buigingen kunnen dus worden aangepast met add-ons van derden (zoals PAC3)'

gui.ppm2.editor.misc.hide_pac3 = 'Entiteiten verbergen wanneer een PAC3 entiteit wordt gebruikt'
gui.ppm2.editor.misc.hide_mane = 'Mane verbergen wanneer een PAC3 entiteit wordt gebruikt'
gui.ppm2.editor.misc.hide_tail = 'Verberg de staart wanneer een PAC3 entiteit wordt gebruikt'
gui.ppm2.editor.misc.hide_socks = 'Sokken verbergen bij gebruik van een PAC3 entiteit'

gui.ppm2.editor.tabs.main = 'Algemeen'
gui.ppm2.editor.tabs.files = 'Bestanden'
gui.ppm2.editor.tabs.old_files = 'Oude Bestanden'
gui.ppm2.editor.tabs.cutiemark = 'Cutiemark'
gui.ppm2.editor.tabs.head = 'Hoofd anatomie'
gui.ppm2.editor.tabs.eyes = 'Ogen'
gui.ppm2.editor.tabs.face = 'Gezicht'
gui.ppm2.editor.tabs.mouth = 'Mond'
gui.ppm2.editor.tabs.left_eye = 'Linker oog'
gui.ppm2.editor.tabs.right_eye = 'Rechter oog'
gui.ppm2.editor.tabs.mane_horn = 'Mane en Hoorn'
gui.ppm2.editor.tabs.mane = 'Mane'
gui.ppm2.editor.tabs.details = 'Details'
gui.ppm2.editor.tabs.url_details = 'URL Details'
gui.ppm2.editor.tabs.url_separated_details = 'URL Gescheiden Details'
gui.ppm2.editor.tabs.ears = 'Oren'
gui.ppm2.editor.tabs.horn = 'Hoorn'
gui.ppm2.editor.tabs.back = 'Rug'
gui.ppm2.editor.tabs.wings = 'Vleugels'
gui.ppm2.editor.tabs.left = 'Links'
gui.ppm2.editor.tabs.right = 'Rechts'
gui.ppm2.editor.tabs.neck = 'Nek'
gui.ppm2.editor.tabs.body = 'Pony lichaam'
gui.ppm2.editor.tabs.tattoos = 'Tatoeages'
gui.ppm2.editor.tabs.tail = 'Staart'
gui.ppm2.editor.tabs.hooves = 'Hoeven anatomie'
gui.ppm2.editor.tabs.bottom_hoof = 'Onder hoef'
gui.ppm2.editor.tabs.legs = 'Benen'
gui.ppm2.editor.tabs.socks = 'Sokken'
gui.ppm2.editor.tabs.newsocks = 'Nieuwe Sokken'
gui.ppm2.editor.tabs.about = 'Over'

gui.ppm2.editor.old_tabs.mane_tail = 'Mane en staart'
gui.ppm2.editor.old_tabs.wings_and_horn_details = 'Vleugels en hoorn details'
gui.ppm2.editor.old_tabs.wings_and_horn = 'Vleugels en hoorn'
gui.ppm2.editor.old_tabs.body_details = 'Lichaam details'
gui.ppm2.editor.old_tabs.mane_tail_detals = 'Mane en staart URL details'

gui.ppm2.editor.cutiemark.display = 'Toon cutiemarkering'
gui.ppm2.editor.cutiemark.type = 'Cutiemark type'
gui.ppm2.editor.cutiemark.size = 'Cutiemark grootte'
gui.ppm2.editor.cutiemark.color = 'Cutiemark kleur'
gui.ppm2.editor.cutiemark.input = 'Cutiemark URL afbeelding invoer veld\nMoet PNG of JPEG zijn (werkt hetzelfde als\nPAC3 URL textuur)'

gui.ppm2.editor.face.eyelashes = 'Wimpers'
gui.ppm2.editor.face.eyelashes_color = 'Wimpers kleur'
gui.ppm2.editor.face.eyelashes_phong = 'Wimpers phong parameters'
gui.ppm2.editor.face.eyebrows_color = 'Wenkbrauwen kleur'
gui.ppm2.editor.face.new_muzzle = 'Gebruik nieuwe snuit voor het mannelijk model'

gui.ppm2.editor.face.nose = 'Neus kleur'
gui.ppm2.editor.face.lips = 'Lippen kleur'
gui.ppm2.editor.face.eyelashes_separate_phong = 'Afzonderlijke wimpers Phong'
gui.ppm2.editor.face.eyebrows_glow = 'Gloeiende wenkbrauwen'
gui.ppm2.editor.face.eyebrows_glow_strength = 'Wenkbrauwen gloed sterkte'
gui.ppm2.editor.face.inherit.lips = 'Neem de Lippen kleur van het lichaam'
gui.ppm2.editor.face.inherit.nose = 'Neem de neus kleur van het lichaam'

gui.ppm2.editor.mouth.fangs = 'Tanden'
gui.ppm2.editor.mouth.alt_fangs = 'Alternatieve hoektanden'
gui.ppm2.editor.mouth.claw = 'Klauwtanden'

gui.ppm2.editor.mouth.teeth = 'Tand kleur'
gui.ppm2.editor.mouth.teeth_phong = 'Tanden phong parameters'
gui.ppm2.editor.mouth.mouth = 'Mond kleur'
gui.ppm2.editor.mouth.mouth_phong = 'Mond phong parameters'
gui.ppm2.editor.mouth.tongue = 'Tong kleur'
gui.ppm2.editor.mouth.tongue_phong = 'Tong phong parameters'

gui.ppm2.editor.mane.type = 'Mane Type'
gui.ppm2.editor.mane.phong = 'Afzonderlijk mane phong instellingen van het lichaam'
gui.ppm2.editor.mane.mane_phong = 'Mane phong parameters'
gui.ppm2.editor.mane.phong_sep = 'Gescheiden bovenste en onderste mane kleuren'
gui.ppm2.editor.mane.up.phong = 'Bovenste Mane Phong instellingen'
gui.ppm2.editor.mane.down.type = 'Onderste Mane Type'
gui.ppm2.editor.mane.down.phong = 'Onderste Mane Phong instellingen'
gui.ppm2.editor.mane.newnotice = 'De volgende opties hebben alleen effect op een nieuw model'

for i = 1, 2
	gui.ppm2.editor.mane['color' .. i] = "Mane kleur #{i}"
	gui.ppm2.editor.mane.up['color' .. i] = "Bovenste mane kleur #{i}"
	gui.ppm2.editor.mane.down['color' .. i] = "Onderste mane kleur #{i}"

for i = 1, 6
	gui.ppm2.editor.mane['detail_color' .. i] = "Mane detail kleur #{i}"
	gui.ppm2.editor.mane.up['detail_color' .. i] = "Bovenste mane kleur #{i}"
	gui.ppm2.editor.mane.down['detail_color' .. i] = "Onderste mane kleur #{i}"

	gui.ppm2.editor.url_mane['desc' .. i] = "Mane URL Detail #{i} invoer veld"
	gui.ppm2.editor.url_mane['color' .. i] = "Mane URL detail kleur #{i}"

	gui.ppm2.editor.url_tail['desc' .. i] = "Staart URL Detail #{i} invoer veld"
	gui.ppm2.editor.url_tail['color' .. i] = "Staart URL detail kleur #{i}"

	gui.ppm2.editor.url_mane.sep.up['desc' .. i] = "Bovenste mane URL Detail #{i} invoer veld"
	gui.ppm2.editor.url_mane.sep.up['color' .. i] = "Bovenste Mane URL detail kleur #{i}"

	gui.ppm2.editor.url_mane.sep.down['desc' .. i] = "Onderste mane URL Detail #{i} invoer veld"
	gui.ppm2.editor.url_mane.sep.down['color' .. i] = "Onderste Mane URL detail kleur #{i}"

gui.ppm2.editor.ears.bat = 'Vleermuis pony oren'
gui.ppm2.editor.ears.size = 'Oren grootte'

gui.ppm2.editor.horn.detail_color = 'Hoorn detail kleur'
gui.ppm2.editor.horn.glowing_detail = 'Glanzende Hoorn Detail'
gui.ppm2.editor.horn.glow_strength = 'Horn gloed sterkte'
gui.ppm2.editor.horn.separate_color = 'Aparte hoornkleur van het lichaam'
gui.ppm2.editor.horn.color = 'Hoorn kleur'
gui.ppm2.editor.horn.horn_phong = 'Hoorn phong parameters'
gui.ppm2.editor.horn.magic = 'Hoorn magie kleur'
gui.ppm2.editor.horn.separate_magic_color = 'Aparte magische kleur van oogkleur'
gui.ppm2.editor.horn.separate = 'Aparte hoornkleur van het lichaam'
gui.ppm2.editor.horn.separate_phong = 'Aparte hoorn phong instellingen van het lichaam'

for i = 1, 3
	gui.ppm2.editor.horn.detail['desc' .. i] = "Hoorn URL detail #{i}"
	gui.ppm2.editor.horn.detail['color' .. i] = "URL Detail kleur #{i}"

gui.ppm2.editor.wings.separate_color = 'Aparte vleugel kleur van het lichaam'
gui.ppm2.editor.wings.color = 'Vleugel color'
gui.ppm2.editor.wings.wings_phong = 'Vleugel phong parameters'
gui.ppm2.editor.wings.separate = 'Aparte vleugel kleur van het lichaam'
gui.ppm2.editor.wings.separate_phong = 'Aparte vleugel phong instellingen van het lichaam'
gui.ppm2.editor.wings.bat_color = 'Vleermuis vleugels kleur'
gui.ppm2.editor.wings.bat_skin_color = 'Vleermuis vleugels huidskleur'
gui.ppm2.editor.wings.bat_skin_phong = 'Vleermuis vleugels huid phong parameters'

gui.ppm2.editor.wings.normal = 'Normale vleugels'
gui.ppm2.editor.wings.bat = 'Vleermuisvleugels'
gui.ppm2.editor.wings.bat_skin = 'Vleermuis vleugels huid'

gui.ppm2.editor.wings.left.size = 'Linker vleugel grootte'
gui.ppm2.editor.wings.left.fwd = 'Linker vleugel voor'
gui.ppm2.editor.wings.left.up = 'Linker vleugel boven'
gui.ppm2.editor.wings.left.inside = 'Linker vleugel binnen'

gui.ppm2.editor.wings.right.size = 'Rechter vleugel grootte'
gui.ppm2.editor.wings.right.fwd = 'Rechter vleugel voor'
gui.ppm2.editor.wings.right.up = 'Rechter vleugel boven'
gui.ppm2.editor.wings.right.inside = 'Rechter vleugel binnen'

for i = 1, 3
	gui.ppm2.editor.wings.details.def['detail' .. i] = "Vleugels URL detail #{i}"
	gui.ppm2.editor.wings.details.def['color' .. i] = "URL Detail kleur #{i}"
	gui.ppm2.editor.wings.details.bat['detail' .. i] = "Vleermuisvleugels URL detail #{i}"
	gui.ppm2.editor.wings.details.bat['color' .. i] = "Vleermuisvleugels URL Detail kleur #{i}"
	gui.ppm2.editor.wings.details.batskin['detail' .. i] = "Vleermuisvleugels huid URL detail #{i}"
	gui.ppm2.editor.wings.details.batskin['color' .. i] = "Vleermuisvleugels huid URL Detail kleur #{i}"

gui.ppm2.editor.neck.height = 'Nek hoogte'

gui.ppm2.editor.body.suit = 'Kostuum'
gui.ppm2.editor.body.color = 'Lichaams kleur'
gui.ppm2.editor.body.body_phong = 'Lichaam phong parameters'
gui.ppm2.editor.body.spine_length = 'Rug lengte'
gui.ppm2.editor.body.url_desc = 'Lichaam detail URL afbeelding invoer veld\nMoet PNG of JPEG zijn (werkt hetzelfde als \nPAC3 URL structuur)'

gui.ppm2.editor.body.disable_hoofsteps = 'Hoef stappen uitschakelen'
gui.ppm2.editor.body.disable_wander_sounds = 'Wandelgeluiden uitschakelen'
gui.ppm2.editor.body.disable_new_step_sounds = 'Aangepaste stapgeluiden uitschakelen'
gui.ppm2.editor.body.disable_jump_sound = 'Spring geluid uitschakelen'
gui.ppm2.editor.body.disable_falldown_sound = 'Neer val geluid uitschakelen'
gui.ppm2.editor.body.call_playerfootstep = 'Noem PlayerFootstep bij elk geluid'
gui.ppm2.editor.body.call_playerfootstep_desc = 'Noem PlayerFootstep haak op elk daadwerkelijk geluid dat u hoort.\nMet behulp van deze optie kunnen andere geïnstalleerde addons vertrouwen op PPM2\'s diepgang\ndie luisteren naar PlayerFootstep haak. Dit moet alleen worden uitgeschakeld wanneer u onbetrouwbare resultaten van andere addons krijgt.\nof uw FPS daalt naar lage waarden omdat een van de geïnstalleerde addons slecht gecodeerd is.'

for i = 1, PPM2.MAX_BODY_DETAILS
	gui.ppm2.editor.body.detail['desc' .. i] = "Detail #{i}"
	gui.ppm2.editor.body.detail['color' .. i] = "Detail kleur #{i}"
	gui.ppm2.editor.body.detail['glow' .. i] = "Detail #{i} gloeit"
	gui.ppm2.editor.body.detail['glow_strength' .. i] = "Detail #{i} gloeikracht"

	gui.ppm2.editor.body.detail.url['desc' .. i] = "Detail #{i}"
	gui.ppm2.editor.body.detail.url['color' .. i] = "Detail kleur #{i}"

gui.ppm2.editor.tattoo.edit_keyboard = 'Bewerken met behulp van het toetsenbord'
gui.ppm2.editor.tattoo.type = 'Type'
gui.ppm2.editor.tattoo.over = 'Tatoeage over het lichaam details'
gui.ppm2.editor.tattoo.glow = 'Tatoeage is gloeiend'
gui.ppm2.editor.tattoo.glow_strength = 'De sterkte van de tatoegering gloed'
gui.ppm2.editor.tattoo.color = 'Kleur van Tattoo'

gui.ppm2.editor.tattoo.tweak.rotate = 'Rotatie'
gui.ppm2.editor.tattoo.tweak.x = 'X Positie'
gui.ppm2.editor.tattoo.tweak.y = 'Y Positie'
gui.ppm2.editor.tattoo.tweak.width = 'Breedte Schaal'
gui.ppm2.editor.tattoo.tweak.height = 'Hoogte schaal'

for i = 1, PPM2.MAX_TATTOOS
	gui.ppm2.editor.tattoo['layer' .. i] = "Tatoegeringslaag #{i}"

gui.ppm2.editor.tail.type = 'Staart type'
gui.ppm2.editor.tail.size = 'Staart grootte'
gui.ppm2.editor.tail.tail_phong = 'Staart phong parameters'
gui.ppm2.editor.tail.separate = 'Aparte staart phong instellingen van het lichaam'

for i = 1, 2
	gui.ppm2.editor.tail['color' .. i] = 'Staart kleur ' .. i

for i = 1, 6
	gui.ppm2.editor.tail['detail' .. i] = "Staart detail kleur #{i}"
	gui.ppm2.editor.tail.url['detail' .. i] = "Staart URL detail #{i}"
	gui.ppm2.editor.tail.url['color' .. i] = "Staart URL detail #{i}"

gui.ppm2.editor.hoof.fluffers = 'Hoef Fluffers'

gui.ppm2.editor.legs.height = 'Benen hoogte'
gui.ppm2.editor.legs.socks.simple = 'Sokken (eenvoudige textuur)'
gui.ppm2.editor.legs.socks.model = 'Sokken (als model)'
gui.ppm2.editor.legs.socks.color = 'Sokken model kleur'
gui.ppm2.editor.legs.socks.socks_phong = 'Sokken phong parameters'
gui.ppm2.editor.legs.socks.texture = 'Sokken textuur'
gui.ppm2.editor.legs.socks.url_texture = 'Sokken URL textuur'

for i = 1, 6
	gui.ppm2.editor.legs.socks['color' .. i] = 'Sokken detail kleur ' .. i

gui.ppm2.editor.legs.newsocks.model = 'Sokken (als een nieuw model)'

for i = 1, 3
	gui.ppm2.editor.legs.newsocks['color' .. i] = 'Nieuwe sokken kleur ' .. i

gui.ppm2.editor.legs.newsocks.url = 'Nieuwe sokken URL textuur'

-- shared editor stuffs

gui.ppm2.editor.tattoo.help = "Om de bewerkmodus te verlaten, drukt u op Escape of klikt u ergens met de muis
Om tatto te verplaatsen, gebruikt u WASD
Om hoger/lager te schalen gebruikt u de pijlen omhoog/omlaag
Om breder/kleiner te schalen gebruik rechts/links pijlen
Om links/rechts te draaien gebruikt u Q/E"

gui.ppm2.editor.reset_value = 'Reset %s'

gui.ppm2.editor.phong.info = 'Meer info over Phong op de wiki'
gui.ppm2.editor.phong.exponent = 'Phong Exponent - hoe sterk reflecterende eigenschap\nvan een ponyhuid is\nZet bijna op nul om een robotachtige uitstraling te krijgen van uw\npony huid'
gui.ppm2.editor.phong.exponent_text = 'Phong Exponent'
gui.ppm2.editor.phong.boost.title = 'Phong Verhogen - vermenigvuldigt spiegelende kaartreflecties'
gui.ppm2.editor.phong.boost.boost = 'Phong Verhogen'
gui.ppm2.editor.phong.tint.title = 'Tint kleur - welke kleuren weerspiegelt in de kaart\nWit - Weerspiegelt alle kleuren\n(In witte kamer - witte spiegelkaart)'
gui.ppm2.editor.phong.tint.tint = 'Tint kleur'
gui.ppm2.editor.phong.frensel.front.title = 'Phong Voorkant - Fresnel 0 graden reflectie hoekvermenigvuldiger'
gui.ppm2.editor.phong.frensel.front.front = 'Phong Voorkant'
gui.ppm2.editor.phong.frensel.middle.title = 'Phong Midden - Fresnel 45 graden reflectie hoekvermenigvuldiger'
gui.ppm2.editor.phong.frensel.middle.front = 'Phong Midden'
gui.ppm2.editor.phong.frensel.sliding.title = 'Phong glijdend - Fresnel 90 graden reflectie hoekvermenigvuldiger'
gui.ppm2.editor.phong.frensel.sliding.front = 'Phong glijdend'
gui.ppm2.editor.phong.lightwarp = 'Lightwarp'
gui.ppm2.editor.phong.url_lightwarp = 'Lightwarp textuur URL invoer\nIt moet 256x16 zijn!'
gui.ppm2.editor.phong.bumpmap = 'Bumpmap URL invoer'

gui.ppm2.editor.info.discord = "Sluit je aan bij DBot's Discord!"
gui.ppm2.editor.info.ponyscape = "PPM/2 is een Ponyscape project"
gui.ppm2.editor.info.creator = "PPM/2 is gemaakt en ontwikkeld door DBot"
gui.ppm2.editor.info.newmodels = "Nieuwe modellen zijn gemaakt door Durpy"
gui.ppm2.editor.info.cppmmodels = "CPPM modellen (inclusief ponyhanden) behoren tot UnkN', 'http://steamcommunity.com/profiles/76561198084938735"
gui.ppm2.editor.info.oldmodels = "Oude modellen zijn van Scentus en anderen"
gui.ppm2.editor.info.bugs = "Heeft u een bug gevonden? Meld hier!"
gui.ppm2.editor.info.sources = "U kunt hier bronnen vinden"
gui.ppm2.editor.info.githubsources = "Of op de GitHub-mirror"
gui.ppm2.editor.info.thanks = "Met dank aan everypony die kritiek heeft geuit,\ngeholpen en getest van PPM/2!"

-- other stuff

info.ppm2.fly.pegasus = 'Je moet een Pegasus of een Alicorn zijn om te vliegen!'
info.ppm2.fly.cannot = 'Je kunt %s nu niet gebruiken.'

gui.ppm2.emotes.sad = 'Verdrietig'
gui.ppm2.emotes.wild = 'Wild'
gui.ppm2.emotes.grin = 'Grijns'
gui.ppm2.emotes.angry = 'Boos'
gui.ppm2.emotes.tongue = ':P'
gui.ppm2.emotes.angrytongue = '>:P'
gui.ppm2.emotes.pff = 'Pffff!'
gui.ppm2.emotes.kitty = ':3'
gui.ppm2.emotes.owo = 'oWo'
gui.ppm2.emotes.ugh = 'Uuugh'
gui.ppm2.emotes.lips = 'Lippen likken'
gui.ppm2.emotes.scrunch = 'Knarsen'
gui.ppm2.emotes.sorry = 'Sorry'
gui.ppm2.emotes.wink = 'Knipoogje'
gui.ppm2.emotes.right_wink = 'Rechter knipoogje'
gui.ppm2.emotes.licking = 'Likken'
gui.ppm2.emotes.suggestive_lips = 'Suggestieve lippen likken'
gui.ppm2.emotes.suggestive_no_tongue = 'Suggestieve w/o tong'
gui.ppm2.emotes.gulp = 'Slikken'
gui.ppm2.emotes.blah = 'Blah blah blah'
gui.ppm2.emotes.happi = 'Blij'
gui.ppm2.emotes.happi_grin = 'Blije grijns'
gui.ppm2.emotes.duck = 'EEND'
gui.ppm2.emotes.ducks = 'DUCK INSANITY'
gui.ppm2.emotes.quack = 'KWAK'
gui.ppm2.emotes.suggestive = 'Suggestieve w/ tong'

message.ppm2.emotes.invalid = 'Geen emotie met ID: %s'

gui.ppm2.editor.intro.text = "Welkom bij mijn nieuwe..... Robosurgeon voor pony's! Het stelt u in staat om........\n" ..
	"hmmm..... een pony te worden, en ja, dit proces is IRREVERSCHAPPELIJK! Maar u hoeft zich geen zorgen te maken,\n" ..
	"u verliest geen van uw hersencellen voor, in en na de operatie, omdat het een zeer zachte operatie uitvoert.....\n\n" ..
	"Eigenlijk heb ik geen idee, jij biologisch wezen! Zijn mechanische handen zullen je omhullen in de strakste knuffels die je ooit hebt gekregen!\n" ..
	"En, alstublieft, niet sterven in het proces, want dit zou VERVALLEN VAN UW LEVENSLANGE GARANTIE veroorzaken..... en je zult geen pony zijn!\n" ..
	"----\n\n\n" ..
	"Let op: Haal Robosurgeon niet uit elkaar. Ieders handen/handschoenen niet in bewegende delen van Robosurgeon..\n" ..
	"Schakel het apparaat niet uit terwijl het werkt.\nProbeer niet te weerstaan aan zijn acties.\n" ..
	"Wees altijd voorzichtig met Robosurgeon\n" ..
	"Sla nooit Robosurgeon op zijn interface.\n" ..
	"DBot's DLibCo neemt geen verantwoordelijkheid voor eventuele schade veroorzaakt door verkeerd gebruik van Robosurgeon.\n" ..
	"De garantie vervalt als de gebruiker sterft.\n" ..
	"Geen terugbetalingen."
gui.ppm2.editor.intro.title = 'Welkom hier, Mens!!'
gui.ppm2.editor.intro.okay = "Ok, ik zal deze licentie toch nooit lezen"

message.ppm2.debug.race_condition = 'Ontvangen NetworkedPonyData voordat de entiteit op de client werd aangemaakt! Wachten.....'

gui.ppm2.spawnmenu.newmodel = 'Spawn nieuw model'
gui.ppm2.spawnmenu.newmodelnj = 'Spawn nieuw nj model'
gui.ppm2.spawnmenu.oldmodel = 'Spawn oud model'
gui.ppm2.spawnmenu.oldmodelnj = 'Spawn oud nj model'
gui.ppm2.spawnmenu.cppmmodel = 'Spawn CPPM model'
gui.ppm2.spawnmenu.cppmmodelnj = 'Spawn CPPM nj model'
gui.ppm2.spawnmenu.cleanup = 'Opruimen ongebruikt models'
gui.ppm2.spawnmenu.reload = 'Herlaad lokale gegevens'
gui.ppm2.spawnmenu.require = 'Gegevens vereisen van de server'
gui.ppm2.spawnmenu.drawhooves = 'Laat hoeven zien als handen'
gui.ppm2.spawnmenu.nohoofsounds = 'Geen hoefgeluiden'
gui.ppm2.spawnmenu.noflexes = 'Schakel flexes uit (emotes)'
gui.ppm2.spawnmenu.advancedmode = 'Activeer de geavanceerde modus voor PPM2 editor'
gui.ppm2.spawnmenu.reflections = 'Activeer reflecties in real-time'
gui.ppm2.spawnmenu.reflections_drawdist = 'Reflecties tekenen afstand'
gui.ppm2.spawnmenu.reflections_renderdist = 'Reflecties laad afstand'
gui.ppm2.spawnmenu.doublejump = 'Dubbel springen activeert vliegen'

tip.ppm2.in_editor = 'In PPM/2 Editor'
tip.ppm2.camera = "%s's PPM/2 Camera"

message.ppm2.queue_notify = '%i texturen staan in de rij om te worden weergegeven'

gui.ppm2.editor.body.bump = 'Bumpmap sterkte'


heredoc example

heredoc example

# ------------------------------------------------------------------------------
# example config format
#
# this is a working draft of things that are valid in a new config language to
# replace json as a config language for Go projects.
#
# comments are a thing now!
# ------------------------------------------------------------------------------

# the whole document is implicitly a namespace, so you can set key value pairs
# at the top level.
first_name: jordan
last_name: orelli

# the bare strings true and false are boolean values
bool_true: true
bool_false: false

# the quoted strings "true" and "false" are string values.  In the unlikely
# event you need literal true and false strings, quote them.
string_true: "true"
string_false: "false"

# bare strings that can be parsed as durations are parsed as durations.
dur_one: 30s
dur_two: 5h3m27s9ms

# lists of things should be supported
items: [
    one
    2
    3.4
    [five; 6 7.8]
]

# objects should be supported
hash: {key: value; other_key: other_value}

other_hash: {
    key_1: one
    key_2: 2
    key_3: 3.4
    key_4: [five; 6 7.8]
}

# we may reference an item that was defined earlier using a sigil
repeat_hash: @hash

# items can be hidden.  i.e., they are only valid in the parse and eval stage
# as intermediate values internal to the config file; they are *not* visible to
# the host program.  This is generally useful for composing larger, more
# complicated things.
@hidden_item: it has a value
visible_item: @hidden_item

@person_one: {
    name: the first name here
    age: 28
    hometown: crooklyn
}

@person_two: {
    name: the second name here
    age: 30
    hometown: tha bronx
}

people: [@person_one @person_two]

# if you need to embed a large block of text, bash-style HERE documents are
# supported.

startup: <<EOF
This is a here document.  The << operator indicates that a label for a heredoc
is incoming.  <<EOF thus begins a heredoc with the label EOF.  A heredoc may be
closed by indicating the label name on a line all by itself.

http://en.wikipedia.org/wiki/Here_document

A heredoc may contain any characters at all and they require no escaping.
# This line that looks a bit like a comment is not a comment; it will be
# included in the output.
{ <-- that didn't start an object
and this won't end one --> }
because there are no types inside of here documents; the whole thing is just
one big string.

This is the last line of the here doc. The next line is the terminator.
EOF

# This comment is outside of the heredoc.


Added Base@className.

Added Base@className.

class Base
	@__inherited: (child) =>
		child.name = child.__name

	new: () =>
		@class = @@
		@className = @@__name
		@type = _.lowerFirst(@@__name)

class Base
	@__inherited: (child) =>
		child.name = child.__name

	new: () =>
		@class = @@
		@type = _.lowerFirst(@@__name)


rss: use abort to avoid autoincrement

rss: use abort to avoid autoincrement

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'
sql = require'lsqlite3'

moduleName = 'rss'

get_db = ->
  db = sql.open 'cache/rss.sql'

  code = db\exec [[
    CREATE TABLE IF NOT EXISTS feed (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT,
      rssurl VARCHAR(1024) NOT NULL,
      url VARCHAR(1024),
      title VARCHAR(1024),
      author VARCHAR(1024),
      etag TEXT,
      lastmodified TEXT
    );

    CREATE TABLE IF NOT EXISTS item (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      feed_id INTEGER NOT NULL,
      guid VARCHAR(64),
      title VARCHAR(1024),
      author VARCHAR(1024),
      url VARCHAR(1024),
      feedurl VARCHAR(1024),
      pubDate TEXT,
      summary TEXT,
      content TEXT,
      FOREIGN KEY(feed_id) REFERENCES feed(id),
      UNIQUE (guid, feed_id)
    );

    CREATE TABLE IF NOT EXISTS subscription (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      destination varchar(100),
      feed_id INTEGER,
      last INTEGER DEFAULT 0,
      created_at timestamp DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY(feed_id) REFERENCES feed(id)
    );
    ]]

  if code != sql.OK
    ivar2\Log 'error', "Error during opendb: #{db\errmsg!}"
    return nil

  return db

prepare = (db, stmt, ...) ->
  code = db\prepare stmt
  unless code
    ivar2\Log 'error', "Error with statement prepare: #{db\errmsg!}"
    return nil
  code\bind_values ...
  return code

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (feedurl, link, title, summary, content) ->
  if feedurl\match 'https://comics.io/my/'

    title = summary\match '
(.-)
' or ''
    img, alt = summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{title} - #{link}"

announce = (id) ->
  db = get_db!
  stmt = prepare db, "SELECT DISTINCT feed.id, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id WHERE feed.id = ?", id
  for row in stmt\rows!
    destination = row[2]

    highest = 0
    -- Check last value
    stmt = prepare db, "SELECT last FROM subscription WHERE feed_id = ? and destination = ?", id, destination
    stmt\step!
    value = stmt\get_values()[1]
    if value and value ~= '' and tonumber(value) and tonumber(value) > 0
      highest = value

    ivar2\Log 'debug', 'Got highest %s, %s, %s', id, destination, highest

    stmt = prepare db, "SELECT feed.id AS feed_id, item.id AS item_id, rssurl, pubDate, name, etag, item.url AS link, item.title, content, summary FROM feed JOIN item on item.feed_id = feed.id WHERE feed.id = ? AND item.id > ? ORDER BY item.id DESC LIMIT 100", id, highest
    out = {}
    for item in stmt\nrows!
      with item
        if .item_id > highest
          highest = .item_id
          table.insert out, "[#{bold .name}]: #{feedSpecific(.rssurl, .link, .title, .summary, .content)}"
    if #out > 0
      ivar2\Msg 'privmsg', destination, ivar2.nick, "RSS "..table.concat(out, ', ')
    -- Update last red
    ins = prepare db, 'UPDATE subscription SET last=? WHERE feed_id = ? and destination = ?', highest, id, destination
    code, err = ins\step!
    code, err = ins\finalize!
  db\close!

poll = ->
  db = get_db!
  channels = {}
  for c,_ in pairs(ivar2.channels)
    channels[c] = true
  for row in db\rows "SELECT DISTINCT feed.id, rssurl, lastmodified, name, etag, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id"
  -- Ignore feeds this particular instance of the bot does not subscribe to
    if not channels[row[6]]
      continue
    id = row[1]
    rssurl = row[2]
    lm = row[3]
    name = row[4]
    etag = row[5]
    headers = {}
    if lm and lm != '' then
      headers['If-Modified-Since'] = lm
    if etag and etag != '' then
      headers['If-None-Match'] = etag

    simplehttp {url:rssurl,headers:headers}, (data, url, response) ->
      -- Reopenn DB because of async
      sdb = get_db!

      lastModified = response.headers['Last-Modified']
      if not lastModified
        lastModified = response.headers['Date']

      -- Unmodified content
      if response.status_code == 304
        -- return from the simplehttp callback
        return

      feed, err = feedparser.parse data
      if err
        ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{url}>"
      else
        title = feed.title
        author = feed.author
        url = feed.link

        -- Update feed with new values
        ins = sdb\prepare 'UPDATE feed SET title=?, author=?, url=?, lastmodified=? WHERE id=?'
        code, err = ins\bind_values title, author, url, lastModified, id
        code, err = ins\step!
        code, err = ins\finalize!

        out = {}
        for i, e in ipairs(feed.entries)
          -- Attempt to get a unique entry ID
          guid = e.guid
          if not guid then guid = e.id
          if not guid then guid = e.link
          if not guid
            ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{url}>"
            break

          --if guid == last
          --  break
          ins = sdb\prepare [[
            INSERT OR ABORT INTO
              item(feed_id, guid, title, author, url, pubDate, content, summary)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?)
          ]]
          if not ins then
            ivar2\Log 'error', "#{moduleName}: Error during inserting entry: <#{err}> feed: <#{name}> with URL <#{url}> and position: #{i}"
          else
            code = ins\bind_values id, guid, e.title, e.author, e.link, e.updated, e.content, e.summary
            code = ins\step!
            code = ins\finalize!
        announce(id)
      sdb\close!
  db\close!

subscribe = (source, destination, name, url) =>
  db = get_db!
  ins = db\prepare "INSERT INTO feed (name, rssurl) VALUES(?, ?)"
  code = ins\bind_values name, url
  code = ins\step!
  code = ins\finalize!
  ins = db\prepare "INSERT INTO subscription (feed_id, destination) VALUES(?, ?)"
  code = ins\bind_values db\last_insert_rowid!, destination
  code = ins\step!
  code = ins\finalize!
  db\close!
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  db = get_db!
  ins = db\prepare "DELETE FROM subscription WHERE feed_id IN (select id from feed where name = ?) and destination = ?"
  code = ins\bind_values name, destination
  code = ins\step!
  code = ins\finalize!
  total_changes = db\total_changes!
  db\close!
  if total_changes < 1
    reply "Wasn't subscribed. But, sure."
  else
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  out = {}
  db = get_db!
  stmt = db\prepare("SELECT name FROM feed JOIN subscription ON feed.id = subscription.feed_id WHERE destination = ?")
  stmt\bind_values destination
  for row in stmt\rows!
    out[#out+1] = row[1]
  if #out > 0 then
    reply "Subscribed to: #{table.concat(out, ', ')}"
  else
    reply "Not subscribed to any feeds."

  db\close!

-- Start the subscribe poller
ivar2\Timer(moduleName, 60*5, 60*5, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll

-- Module for subscribing to RSS and announce to channels
feedparser = require 'feedparser' -- http://feedparser.luaforge.net/
{:simplehttp, :json, :urlEncode, :bold} = require'util'
html2unicode = require 'html'
sql = require'lsqlite3'

moduleName = 'rss'

get_db = ->
  db = sql.open 'cache/rss.sql'

  code = db\exec [[
    CREATE TABLE IF NOT EXISTS feed (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT,
      rssurl VARCHAR(1024) NOT NULL,
      url VARCHAR(1024),
      title VARCHAR(1024),
      author VARCHAR(1024),
      etag TEXT,
      lastmodified TEXT
    );

    CREATE TABLE IF NOT EXISTS item (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      feed_id INTEGER NOT NULL,
      guid VARCHAR(64),
      title VARCHAR(1024),
      author VARCHAR(1024),
      url VARCHAR(1024),
      feedurl VARCHAR(1024),
      pubDate TEXT,
      summary TEXT,
      content TEXT,
      FOREIGN KEY(feed_id) REFERENCES feed(id),
      UNIQUE (guid, feed_id)
    );

    CREATE TABLE IF NOT EXISTS subscription (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      destination varchar(100),
      feed_id INTEGER,
      last INTEGER DEFAULT 0,
      created_at timestamp DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY(feed_id) REFERENCES feed(id)
    );
    ]]

  if code != sql.OK
    ivar2\Log 'error', "Error during opendb: #{db\errmsg!}"
    return nil

  return db

prepare = (db, stmt, ...) ->
  code = db\prepare stmt
  unless code
    ivar2\Log 'error', "Error with statement prepare: #{db\errmsg!}"
    return nil
  code\bind_values ...
  return code

-- Function that add functionality to extract specific information for specific sites
feedSpecific = (feedurl, link, title, summary, content) ->
  if feedurl\match 'https://comics.io/my/'

    title = summary\match '
(.-)
' or ''
    img, alt = summary\match '
'
    return "#{html2unicode title} #{img} #{html2unicode alt}"

  return "#{title} - #{link}"

announce = (id) ->
  db = get_db!
  stmt = prepare db, "SELECT DISTINCT feed.id, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id WHERE feed.id = ?", id
  for row in stmt\rows!
    destination = row[2]

    highest = 0
    -- Check last value
    stmt = prepare db, "SELECT last FROM subscription WHERE feed_id = ? and destination = ?", id, destination
    stmt\step!
    value = stmt\get_values()[1]
    if value and value ~= '' and tonumber(value) and tonumber(value) > 0
      highest = value

    ivar2\Log 'debug', 'Got highest %s, %s, %s', id, destination, highest

    stmt = prepare db, "SELECT feed.id AS feed_id, item.id AS item_id, rssurl, pubDate, name, etag, item.url AS link, item.title, content, summary FROM feed JOIN item on item.feed_id = feed.id WHERE feed.id = ? AND item.id > ? ORDER BY item.id DESC LIMIT 100", id, highest
    out = {}
    for item in stmt\nrows!
      with item
        if .item_id > highest
          highest = .item_id
          table.insert out, "[#{bold .name}]: #{feedSpecific(.rssurl, .link, .title, .summary, .content)}"
    if #out > 0
      ivar2\Msg 'privmsg', destination, ivar2.nick, "RSS "..table.concat(out, ', ')
    -- Update last red
    ins = prepare db, 'UPDATE subscription SET last=? WHERE feed_id = ? and destination = ?', highest, id, destination
    code, err = ins\step!
    code, err = ins\finalize!
  db\close!

poll = ->
  db = get_db!
  channels = {}
  for c,_ in pairs(ivar2.channels)
    channels[c] = true
  for row in db\rows "SELECT DISTINCT feed.id, rssurl, lastmodified, name, etag, destination FROM feed JOIN subscription ON subscription.feed_id = feed.id"
  -- Ignore feeds this particular instance of the bot does not subscribe to
    if not channels[row[6]]
      continue
    id = row[1]
    rssurl = row[2]
    lm = row[3]
    name = row[4]
    etag = row[5]
    headers = {}
    if lm and lm != '' then
      headers['If-Modified-Since'] = lm
    if etag and etag != '' then
      headers['If-None-Match'] = etag

    simplehttp {url:rssurl,headers:headers}, (data, url, response) ->
      -- Reopenn DB because of async
      sdb = get_db!

      lastModified = response.headers['Last-Modified']
      if not lastModified
        lastModified = response.headers['Date']

      -- Unmodified content
      if response.status_code == 304
        -- return from the simplehttp callback
        return

      feed, err = feedparser.parse data
      if err
        ivar2\Log 'error', "#{moduleName}: Error during parsing: <#{err}> data for feed: <#{name}> with URL <#{url}>"
      else
        title = feed.title
        author = feed.author
        url = feed.link

        -- Update feed with new values
        ins = sdb\prepare 'UPDATE feed SET title=?, author=?, url=?, lastmodified=? WHERE id=?'
        code, err = ins\bind_values title, author, url, lastModified, id
        code, err = ins\step!
        code, err = ins\finalize!

        out = {}
        for i, e in ipairs(feed.entries)
          -- Attempt to get a unique entry ID
          guid = e.guid
          if not guid then guid = e.id
          if not guid then guid = e.link
          if not guid
            ivar2\Log 'error', "#{moduleName}: No GUID when parsing entry: <#{e}> data for feed: <#{name}> with URL <#{url}>"
            break

          --if guid == last
          --  break
          ins = sdb\prepare [[
            INSERT OR IGNORE INTO
              item(feed_id, guid, title, author, url, pubDate, content, summary)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?)
          ]]
          if not ins then
            ivar2\Log 'error', "#{moduleName}: Error during inserting entry: <#{err}> feed: <#{name}> with URL <#{url}> and position: #{i}"
          else
            code = ins\bind_values id, guid, e.title, e.author, e.link, e.updated, e.content, e.summary
            code = ins\step!
            code = ins\finalize!
        announce(id)
      sdb\close!
  db\close!

subscribe = (source, destination, name, url) =>
  db = get_db!
  ins = db\prepare "INSERT INTO feed (name, rssurl) VALUES(?, ?)"
  code = ins\bind_values name, url
  code = ins\step!
  code = ins\finalize!
  ins = db\prepare "INSERT INTO subscription (feed_id, destination) VALUES(?, ?)"
  code = ins\bind_values db\last_insert_rowid!, destination
  code = ins\step!
  code = ins\finalize!
  db\close!
  reply "Ok. Subscribed to #{bold name}"
  poll!

unsubscribe = (source, destination, name) =>
  db = get_db!
  ins = db\prepare "DELETE FROM subscription WHERE feed_id IN (select id from feed where name = ?) and destination = ?"
  code = ins\bind_values name, destination
  code = ins\step!
  code = ins\finalize!
  total_changes = db\total_changes!
  db\close!
  if total_changes < 1
    reply "Wasn't subscribed. But, sure."
  else
    reply "Ok. Stopped caring about #{bold name}"

list = (source, destination) =>
  out = {}
  db = get_db!
  stmt = db\prepare("SELECT name FROM feed JOIN subscription ON feed.id = subscription.feed_id WHERE destination = ?")
  stmt\bind_values destination
  for row in stmt\rows!
    out[#out+1] = row[1]
  if #out > 0 then
    reply "Subscribed to: #{table.concat(out, ', ')}"
  else
    reply "Not subscribed to any feeds."

  db\close!

-- Start the subscribe poller
ivar2\Timer(moduleName, 60*5, 60*5, poll)

PRIVMSG:
  '^%prss latest (.*)': getLatest
  '^%prss subscribe (.-) (.*)': subscribe
  '^%prss unsubscribe (.*)': unsubscribe
  '^%prss list': list
  -- for debugging
  '^%prss poll': poll


modules: use say function where appropriate

Former-commit-id: 228c006821ce8dc34dd2244980c70fca627651fa [formerly c654c551808c0fde80b3a6fa67535b0a07d18143]
Former-commit-id: 9c90fb17c33e6ecc1c1c654b967248f2eae7d7e9

FABULOUS_COLORS = {4, 7, 9, 10, 12, 13, 6}

i = 0

gayify = (s) ->
  if s == "" return ""
  s\gsub "[%z\1-\127\194-\244][\128-\191]*", (c) ->
    ret = string.format("\03%02d%s", FABULOUS_COLORS[i + 1], c)
    i = (i + 1) % #FABULOUS_COLORS
    return ret

PRIVMSG:
  '^%pgay (.+)$': (source, destination, arg) =>
      say gayify arg

FABULOUS_COLORS = {4, 7, 9, 10, 12, 13, 6}

i = 0

gayify = (s) ->
  if s == "" return ""
  s\gsub "[%z\1-\127\194-\244][\128-\191]*", (c) ->
    ret = string.format("\03%02d%s", FABULOUS_COLORS[i + 1], c)
    i = (i + 1) % #FABULOUS_COLORS
    return ret

PRIVMSG:
  '^%pgay (.+)$': (source, destination, arg) =>
      @Msg 'privmsg', destination, source, gayify arg


Moar fixes to bodygroup controller

Moar fixes to bodygroup controller

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

class DefaultBodygroupController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = 1
    @BODYGROUP_HORN = 2
    @BODYGROUP_WINGS = 3
    @BODYGROUP_MANE_UPPER = 4
    @BODYGROUP_MANE_LOWER = 5
    @BODYGROUP_TAIL = 6
    @BODYGROUP_CMARK = 7
    @BODYGROUP_EYELASH = 8

    @NEXT_OBJ_ID = 0

    @COOLDOWN_TIME = 5
    @COOLDOWN_MAX_COUNT = 4

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1
        @SocksModelUpdateCooldown = 0
        @SocksModelUpdateCount = 0

    UpdateCooldowns: =>
        if CLIENT
            @SocksModelUpdateCooldown = 0
            @SocksModelUpdateCount = 0
        rTime = RealTime()
        if @SocksModelUpdateCooldown < rTime
            @SocksModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @SocksModelUpdateCount = 0

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    CreateSocksModel: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @socksModel if IsValid(@socksModel)
        @UpdateCooldowns()
        return NULL if @SocksModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @SocksModelUpdateCount += 1

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isSocks and ent.manePlayer == @ent
                @socksModel = ent
                return ent

        model = 'models/props_pony/ppm/cosmetics/ppm_socks.mdl'

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @socksModel = ents.Create('prop_dynamic') if SERVER
        @socksModel = ClientsideModel(model) if CLIENT
        with @socksModel
            .isPonyPropModel = true
            .isSocks = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@socksModel)
                @GetData()\SetSocksModel(@socksModel)
                @ent\SetNWEntity('PPM2.SocksModel', @socksModel) if IsValid(@ent)
        else
            @GetData()\SetSocksModel(@socksModel)

        return @socksModel
    CreateSocksModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        return @socksModel

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        return unless IsValid(targetEnt)
        socks = @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
        if IsValid(socks)
            socks\SetParent(targetEnt)
            socks\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    GetSocks: => @socksModel or NULL

    ApplyRace: =>
        return unless @isValid
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)

    ResetBodygroups: =>
        return unless @isValid
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
        @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
        @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
        @ApplyRace()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()

    Remove: =>
        @socksModel\Remove() if IsValid(@socksModel)
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        switch state\GetKey()
            when 'ManeType'
                @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
            when 'ManeTypeLower'
                @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
            when 'TailType'
                @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
            when 'TailSize'
                size = state\GetValue()
                vec = Vector(size, size, size)
                @ent\ManipulateBoneScale(@@TAIL_BONE1, vec)
                @ent\ManipulateBoneScale(@@TAIL_BONE2, vec)
                @ent\ManipulateBoneScale(@@TAIL_BONE3, vec)
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
            when 'Race'
                @ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

    new: (...) => super(...)

    ApplyRace: =>
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 2)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 3)

class NewBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = -1
    @BODYGROUP_HORN = 1
    @BODYGROUP_WINGS = 2

    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    new: (...) =>
        super(...)
        @UpperManeModelUpdateCooldown = 0
        @UpperManeModelUpdateCount = 0
        @LowerManeModelUpdateCooldown = 0
        @LowerManeModelUpdateCount = 0
        @TailModelUpdateCooldown = 0
        @TailModelUpdateCount = 0

    UpdateCooldowns: =>
        if CLIENT
            @UpperManeModelUpdateCooldown = 0
            @UpperManeModelUpdateCount = 0
            @LowerManeModelUpdateCooldown = 0
            @LowerManeModelUpdateCount = 0
            @TailModelUpdateCooldown = 0
            @TailModelUpdateCount = 0
        rTime = RealTime()
        if @UpperManeModelUpdateCooldown > rTime
            @UpperManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @UpperManeModelUpdateCount = 0
        if @LowerManeModelUpdateCooldown > rTime
            @LowerManeModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @LowerManeModelUpdateCount = 0
        if @TailModelUpdateCooldown > rTime
            @TailModelUpdateCooldown = rTime + @@COOLDOWN_TIME
            @TailModelUpdateCount = 0

    CreateUpperManeModel: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelUP if IsValid(@maneModelUP)
        @UpdateCooldowns()
        return NULL if @UpperManeModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @UpperManeModelUpdateCount += 1

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.upperMane and ent.manePlayer == @ent
                @maneModelUP = ent
                return ent

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
                @ent\SetNWEntity('PPM2.UpperManeModel', @maneModelUP) if IsValid(@ent)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @maneModelLower if IsValid(@maneModelLower)
        @UpdateCooldowns()
        return NULL if @LowerManeModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @LowerManeModelUpdateCount += 1

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.lowerMane and ent.manePlayer == @ent
                @maneModelLower = ent
                return ent

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
                @ent\SetNWEntity('PPM2.LowerManeModel', @maneModelLower) if IsValid(@ent)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        return @tailModel if IsValid(@tailModel)
        @UpdateCooldowns()
        return NULL if @TailModelUpdateCount > @@COOLDOWN_MAX_COUNT
        @TailModelUpdateCount += 1

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isTail and ent.manePlayer == @ent
                @tailModel = ent
                return ent

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@tailModel)
                @GetData()\SetTailModel(@tailModel)
                @ent\SetNWEntity('PPM2.TailModel', @tailModel) if IsValid(@ent)
        else
            @GetData()\SetTailModel(@tailModel)

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        return @tailModel

    GetUpperMane: => @maneModelUP or NULL
    GetLowerMane: => @maneModelLower or NULL
    GetTail: => @tailModel or NULL

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        super(targetEnt)
        return unless IsValid(targetEnt)
        maneUpper = @CreateUpperManeModelIfNotExists()
        maneLower = @CreateLowerManeModelIfNotExists()
        tail = @CreateTailModelIfNotExists()
        if IsValid(maneUpper)
            maneUpper\SetParent(targetEnt)
            maneUpper\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(maneLower)
            maneLower\SetParent(targetEnt)
            maneLower\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(tail)
            tail\SetParent(targetEnt)
            tail\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    UpdateUpperMane: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        return NULL if not IsValid(@maneModelUP)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model)
        @maneModelUP\SetBodygroup(1, bodygroupID)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        return NULL if not IsValid(@maneModelLower)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model)
        @maneModelLower\SetBodygroup(1, bodygroupID)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        return NULL if not IsValid(@tailModel)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model)
        @tailModel\SetBodygroup(1, bodygroupID)
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

    ResetBodygroups: =>
        return unless IsValid(@ent)
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, 0)
        super()
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE,          @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS,         @GetData()\GetFangs() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GetData()\GetClawTeeth() and 1 or 0)
        @ApplyRace()
        @CreateUpperManeModel()
        @CreateLowerManeModel()
        @CreateTailModel()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()

    DataChanges: (state) =>
        return unless @isValid
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                @ent\SetFlexWeight(@@FLEX_ID_MALE, @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
            when 'BatPonyEars'
                @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
            when 'Fangs'
                @ent\SetFlexWeight(@@FLEX_ID_FANGS, @GetData()\GetFangs() and 1 or 0)
            when 'ClawTeeth'
                @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GetData()\GetClawTeeth() and 1 or 0)
            when 'ManeTypeNew'
                @UpdateUpperMane()
            when 'ManeTypeLowerNew'
                @UpdateLowerMane()
            when 'TailTypeNew'
                @UpdateTailModel()
            when 'TailSize'
                @UpdateTailModel()
            when 'Race'
                @ApplyRace()
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
    Remove: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodugroupController = (model = 'models/ppm/player_default_base.mdl') -> DefaultBodygroupController.AVALIABLE_CONTROLLERS[model\lower()] or DefaultBodygroupController

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

PPM2.BODYGROUP_SKELETON = 0
PPM2.BODYGROUP_GENDER = 1
PPM2.BODYGROUP_HORN = 2
PPM2.BODYGROUP_WINGS = 3
PPM2.BODYGROUP_MANE_UPPER = 4
PPM2.BODYGROUP_MANE_LOWER = 5
PPM2.BODYGROUP_TAIL = 6
PPM2.BODYGROUP_CMARK = 7
PPM2.BODYGROUP_EYELASH = 8

class DefaultBodygroupController
    @AVALIABLE_CONTROLLERS = {}
    @MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl'}
    @__inherited: (child) =>
        child.MODELS_HASH = {mod, true for mod in *child.MODELS}
        @AVALIABLE_CONTROLLERS[mod] = child for mod in *child.MODELS
    @__inherited(@)

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = 1
    @BODYGROUP_HORN = 2
    @BODYGROUP_WINGS = 3
    @BODYGROUP_MANE_UPPER = 4
    @BODYGROUP_MANE_LOWER = 5
    @BODYGROUP_TAIL = 6
    @BODYGROUP_CMARK = 7
    @BODYGROUP_EYELASH = 8

    @NEXT_OBJ_ID = 0

    new: (controller) =>
        @isValid = true
        @ent = controller.ent
        @entID = controller.entID
        @controller = controller
        @objID = @@NEXT_OBJ_ID
        @@NEXT_OBJ_ID += 1

    __tostring: => "[#{@@__name}:#{@objID}|#{@ent}]"
    IsValid: => @isValid
    GetData: => @controller
    GetEntity: => @ent
    GetEntityID: => @entID
    GetDataID: => @entID

    @ATTACHMENT_EYES = 4
    @ATTACHMENT_EYES_NAME = 'eyes'

    CreateSocksModel: =>
        return NULL unless @isValid
        @socksModel\Remove() if IsValid(@socksModel)

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isSocks and ent.manePlayer == @ent
                ent\Remove()

        model = 'models/props_pony/ppm/cosmetics/ppm_socks.mdl'

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @socksModel = ents.Create('prop_dynamic') if SERVER
        @socksModel = ClientsideModel(model) if CLIENT
        with @socksModel
            .isPonyPropModel = true
            .isSocks = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@socksModel)
                @GetData()\SetSocksModel(@socksModel)
                @ent\SetNWEntity('PPM2.SocksModel', @socksModel) if IsValid(@ent)
        else
            @GetData()\SetSocksModel(@socksModel)

        return @socksModel
    CreateSocksModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateSocksModel() if not IsValid(@socksModel)
        return @socksModel

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        return unless IsValid(targetEnt)
        socks = @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()
        if IsValid(socks)
            socks\SetParent(targetEnt)
            socks\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    GetSocks: => @socksModel or NULL

    ApplyRace: =>
        return unless @isValid
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)

    ResetBodygroups: =>
        return unless @isValid
        for grp in *@ent\GetBodyGroups()
            @ent\SetBodygroup(grp.id, 0)
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
        @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
        @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
        @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
        @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
        @ApplyRace()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()

    Remove: =>
        @socksModel\Remove() if IsValid(@socksModel)
        @ResetBodygroups()
        @isValid = false

    @TAIL_BONE1 = 38
    @TAIL_BONE2 = 39
    @TAIL_BONE3 = 40
    DataChanges: (state) =>
        return unless @isValid
        switch state\GetKey()
            when 'ManeType'
                @ent\SetBodygroup(@@BODYGROUP_MANE_UPPER, @GetData()\GetManeType())
            when 'ManeTypeLower'
                @ent\SetBodygroup(@@BODYGROUP_MANE_LOWER, @GetData()\GetManeTypeLower())
            when 'TailType'
                @ent\SetBodygroup(@@BODYGROUP_TAIL, @GetData()\GetTailType())
            when 'TailSize'
                size = state\GetValue()
                vec = Vector(size, size, size)
                @ent\ManipulateBoneScale(@@TAIL_BONE1, vec)
                @ent\ManipulateBoneScale(@@TAIL_BONE2, vec)
                @ent\ManipulateBoneScale(@@TAIL_BONE3, vec)
            when 'EyelashType'
                @ent\SetBodygroup(@@BODYGROUP_EYELASH, @GetData()\GetEyelashType())
            when 'Gender'
                @ent\SetBodygroup(@@BODYGROUP_GENDER, @GetData()\GetGender())
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
            when 'Race'
                @ApplyRace()

class CPPMBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

    new: (...) => super(...)

    ApplyRace: =>
        switch @GetData()\GetRace()
            when PPM2.RACE_EARTH
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_PEGASUS
                @ent\SetBodygroup(@@BODYGROUP_HORN, 1)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 0)
            when PPM2.RACE_UNICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 0)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 1)
            when PPM2.RACE_ALICORN
                @ent\SetBodygroup(@@BODYGROUP_HORN, 2)
                @ent\SetBodygroup(@@BODYGROUP_WINGS, 3)

class NewBodygroupController extends DefaultBodygroupController
    @MODELS = {'models/ppm/player_default_base_new.mdl'}

    @BODYGROUP_SKELETON = 0
    @BODYGROUP_GENDER = -1
    @BODYGROUP_HORN = 1
    @BODYGROUP_WINGS = 2

    __tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

    new: (...) =>
        super(...)
        @UpperManeModelUpdateCooldown = 0
        @UpperManeModelUpdateCount = 0
        @LowerManeModelUpdateCooldown = 0
        @LowerManeModelUpdateCount = 0
        @TailModelUpdateCooldown = 0
        @TailModelUpdateCount = 0

    UpdateCooldowns: =>
        if CLIENT
            @UpperManeModelUpdateCooldown = 0
            @UpperManeModelUpdateCount = 0
            @LowerManeModelUpdateCooldown = 0
            @LowerManeModelUpdateCount = 0
            @TailModelUpdateCooldown = 0
            @TailModelUpdateCount = 0
        rTime = RealTime()
        if @UpperManeModelUpdateCooldown > rTime
            @UpperManeModelUpdateCooldown = rTime + 5
            @UpperManeModelUpdateCount = 0
        if @LowerManeModelUpdateCooldown > rTime
            @LowerManeModelUpdateCooldown = rTime + 5
            @LowerManeModelUpdateCount = 0
        if @TailModelUpdateCooldown > rTime
            @TailModelUpdateCooldown = rTime + 5
            @TailModelUpdateCount = 0

    CreateUpperManeModel: =>
        return NULL unless @isValid
        @UpdateCooldowns()
        return NULL if @UpperManeModelUpdateCount > 4
        @UpperManeModelUpdateCount += 1
        @maneModelUP\Remove() if IsValid(@maneModelUP)

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.upperMane and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelUP = ents.Create('prop_dynamic') if SERVER
        @maneModelUP = ClientsideModel(model) if CLIENT
        with @maneModelUP
            .isPonyPropModel = true
            .upperMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelUP)
                @GetData()\SetUpperManeModel(@maneModelUP)
                @ent\SetNWEntity('PPM2.UpperManeModel', @maneModelUP) if IsValid(@ent)
        else
            @GetData()\SetUpperManeModel(@maneModelUP)

        return @maneModelUP
    CreateLowerManeModel: =>
        return NULL unless @isValid
        @UpdateCooldowns()
        return NULL if @LowerManeModelUpdateCount > 4
        @LowerManeModelUpdateCount += 1
        @maneModelLower\Remove() if IsValid(@maneModelLower)

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.lowerMane and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @maneModelLower = ents.Create('prop_dynamic') if SERVER
        @maneModelLower = ClientsideModel(model) if CLIENT
        with @maneModelLower
            .isPonyPropModel = true
            .lowerMane = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@maneModelLower)
                @GetData()\SetLowerManeModel(@maneModelLower)
                @ent\SetNWEntity('PPM2.LowerManeModel', @maneModelLower) if IsValid(@ent)
        else
            @GetData()\SetLowerManeModel(@maneModelLower)

        return @maneModelLower
    CreateTailModel: =>
        return NULL unless @isValid
        @UpdateCooldowns()
        return NULL if @TailModelUpdateCount > 4
        @TailModelUpdateCount += 1
        @tailModel\Remove() if IsValid(@tailModel)

        for ent in *ents.GetAll()
            if ent.isPonyPropModel and ent.isTail and ent.manePlayer == @ent
                ent\Remove()

        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"

        return if CLIENT and @GetData()\IsGoingToNetwork()
        @tailModel = ents.Create('prop_dynamic') if SERVER
        @tailModel = ClientsideModel(model) if CLIENT
        with @tailModel
            .isPonyPropModel = true
            .isTail = true
            .manePlayer = @ent
            \SetModel(model)
            \SetPos(@ent\GetPos())
            \Spawn()
            \Activate()
            \SetBodygroup(1, bodygroupID)
            \SetParent(@ent)
            \Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
            \AddEffects(EF_BONEMERGE)

        if SERVER
            timer.Simple .5, ->
                return unless IsValid(@tailModel)
                @GetData()\SetTailModel(@tailModel)
                @ent\SetNWEntity('PPM2.TailModel', @tailModel) if IsValid(@ent)
        else
            @GetData()\SetTailModel(@tailModel)

        return @tailModel

    CreateUpperManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModel() if not IsValid(@maneModelUP)
        return @maneModelUP
    CreateLowerManeModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModel() if not IsValid(@maneModelLower)
        return @maneModelLower
    CreateTailModelIfNotExists: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModel() if not IsValid(@tailModel)
        return @tailModel

    GetUpperMane: => @maneModelUP or NULL
    GetLowerMane: => @maneModelLower or NULL
    GetTail: => @tailModel or NULL

    MergeModels: (targetEnt = NULL) =>
        return unless @isValid
        super(targetEnt)
        return unless IsValid(targetEnt)
        maneUpper = @CreateUpperManeModelIfNotExists()
        maneLower = @CreateLowerManeModelIfNotExists()
        tail = @CreateTailModelIfNotExists()
        if IsValid(maneUpper)
            maneUpper\SetParent(targetEnt)
            maneUpper\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(maneLower)
            maneLower\SetParent(targetEnt)
            maneLower\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER
        if IsValid(tail)
            tail\SetParent(targetEnt)
            tail\Fire('SetParentAttachment', @@ATTACHMENT_EYES_NAME) if SERVER

    UpdateUpperMane: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateUpperManeModelIfNotExists()
        return NULL if not IsValid(@maneModelUP)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetupper#{modelID}.mdl"
        @maneModelUP\SetModel(model)
        @maneModelUP\SetBodygroup(1, bodygroupID)
        return @maneModelUP
    UpdateLowerMane: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateLowerManeModelIfNotExists()
        return NULL if not IsValid(@maneModelLower)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetManeTypeLowerNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_manesetlower#{modelID}.mdl"
        @maneModelLower\SetModel(model)
        @maneModelLower\SetBodygroup(1, bodygroupID)
        return @maneModelLower
    UpdateTailModel: =>
        return NULL unless @isValid
        return NULL if CLIENT and @GetData()\IsGoingToNetwork()
        @CreateTailModelIfNotExists()
        return NULL if not IsValid(@tailModel)
        modelID, bodygroupID = PPM2.TransformNewModelID(@GetData()\GetTailTypeNew())
        modelID = "0" .. modelID if modelID < 10
        model = "models/ppm/hair/ppm_tailset#{modelID}.mdl"
        @tailModel\SetModel(model)
        @tailModel\SetBodygroup(1, bodygroupID)
        @tailModel\SetModelScale(@GetData()\GetTailSize())
        return @tailModel

    @FLEX_ID_EYELASHES = 16
    @FLEX_ID_MALE = 25
    @FLEX_ID_BAT_PONY_EARS = 28
    @FLEX_ID_FANGS = 31
    @FLEX_ID_CLAW_TEETH = 30

    ResetBodygroups: =>
        return unless IsValid(@ent)
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE, 0)
        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS, 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, 0)
        super()
    ApplyBodygroups: =>
        return unless @isValid
        @ResetBodygroups()
        @ent\SetFlexWeight(@@FLEX_ID_EYELASHES,     @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_MALE,          @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_FANGS,         @GetData()\GetFangs() and 1 or 0)
        @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH,    @GetData()\GetClawTeeth() and 1 or 0)
        @ApplyRace()
        @CreateUpperManeModel()
        @CreateLowerManeModel()
        @CreateTailModel()
        @CreateSocksModelIfNotExists() if @GetData()\GetSocksAsModel()

    DataChanges: (state) =>
        return unless @isValid
        switch state\GetKey()
            when 'EyelashType'
                @ent\SetFlexWeight(@@FLEX_ID_EYELASHES, @GetData()\GetEyelashType() == PPM2.EYELASHES_NONE and 1 or 0)
            when 'Gender'
                @ent\SetFlexWeight(@@FLEX_ID_MALE, @GetData()\GetGender() == PPM2.GENDER_MALE and 1 or 0)
            when 'BatPonyEars'
                @ent\SetFlexWeight(@@FLEX_ID_BAT_PONY_EARS, @GetData()\GetBatPonyEars() and 1 or 0)
            when 'Fangs'
                @ent\SetFlexWeight(@@FLEX_ID_FANGS, @GetData()\GetFangs() and 1 or 0)
            when 'ClawTeeth'
                @ent\SetFlexWeight(@@FLEX_ID_CLAW_TEETH, @GetData()\GetClawTeeth() and 1 or 0)
            when 'ManeTypeNew'
                @UpdateUpperMane()
            when 'ManeTypeLowerNew'
                @UpdateLowerMane()
            when 'TailTypeNew'
                @UpdateTailModel()
            when 'TailSize'
                @UpdateTailModel()
            when 'Race'
                @ApplyRace()
            when 'SocksAsModel'
                if state\GetValue()
                    @CreateSocksModelIfNotExists()
                else
                    @socksModel\Remove() if IsValid(@socksModel)
    Remove: =>
        @maneModelUP\Remove() if IsValid(@maneModelUP)
        @maneModelLower\Remove() if IsValid(@maneModelLower)
        @tailModel\Remove() if IsValid(@tailModel)
        super()

PPM2.CPPMBodygroupController = CPPMBodygroupController
PPM2.DefaultBodygroupController = DefaultBodygroupController

PPM2.GetBodugroupController = (model = 'models/ppm/player_default_base.mdl') -> DefaultBodygroupController.AVALIABLE_CONTROLLERS[model\lower()] or DefaultBodygroupController