Jump to content

Kors

Recommended Posts

meta.xml

<meta>
	<info description="judo" author="judo" type="script" version="0.3.0" />

	<!-- If radio3d_playlist resource exists, then it's included. Actually only clientside? -->
	<include resource="radio3d_playlist" />

	<script src="server_player.lua" type="server" />
    <script src="radio3d_client.lua" type="client" />
	<file src="music/placeholder.mp3" />
	<export function="changeSetting" type="server"/>
	<settings>

    	<setting name="*sound3d_min_distance" value="8"
			friendlyname="Minimum Distance"
			group="Sound Distance"
			accept="0-1000"
			desc="Distance when sound will start get more quiet when moving away from sound source."
		/>

		<setting name="*sound3d_max_distance" value="15"
			friendlyname="Maximum Distance"
			group="Sound Distance"
			accept="0-1000"
			desc="Maximum distance, can cut down the area the sound is heared too."
		/>

		<setting name="*sound3d_outside_volume_vehicle" value="50"
			friendlyname="Volume of other Vehicles"
			group="Sound Volumes"
			accept="0-100"
			desc="How loud do you want to hear other vehicles while in your car? Percent."
		/>

		<setting name="*sound3d_outside_volume_foot" value="70"
			friendlyname="Volume on foot"
			group="Sound Volumes"
			accept="0-100"
			desc="How loud do you want to hear other vehicles while being on foot? Percent."
		/>

		<setting name="*sound3d_own_vehicle_volume" value="100"
			friendlyname="Own vehicle volume"
			group="Sound Volumes"
			accept="0-100"
			desc="How loud do you want to hear own vehicle while being in it? Percent."
		/>

		<setting name="*sound3d_radio_off_on_vehicle_exit" value="false"
			friendlyname="Radio off on vehicle Exit"
			group="Preferences"
			accept="false,true"
			desc="Turn off radio when exiting vehicle?"
		/>

		<setting name="*sound3d_radio_off_on_blow_up" value="true"
			friendlyname="Radio off on vehicle blow"
			group="Preferences"
			accept="false,true"
			desc="Turn off radio when vehicle blows up?"
		/>

        <setting name="*playlist_title" value="Choose Song"
			friendlyname="Playlist Title"
			group="Text"
			accept="*"
			examples="Choose Song, Playlist, etc"
			desc="The title appearing in top of the playlist"
		/>

        <setting name="*close_playlist_desc" value="Press 0 to close the playlist"
			friendlyname="Close Playlist"
			group="Text"
			accept="*"
			examples="Press 0 to close the playlist, 0 to close"
			desc="The description appearing in bottom of the playlist"
		/>

        <setting name="*no_music" value="No music"
			friendlyname="No Music entry"
			group="Text"
			accept="*"
			examples="no music, disable"
			desc="Playlist entry to disable music"
		/>

        <setting name="*placeholder" value="Placeholder"
			friendlyname="Placeholder"
			group="Text"
			accept="*"
			examples="Placeholder, Default, Beat Loop"
			desc="Name of Placeholder playlist entry"
		/>

		<setting name="*loop" value="true"
			friendlyname="Loop a song"
			group="Preferences"
			accept="false,true"
			desc="Loop an entry infinitely or not (might be better to set false sometimes). The song element would still stay, and playlist entry would be highlighted, so isn't working exactly as should."
		/>

		<setting name="*close_on_selection" value="true"
			friendlyname="Close playlist when selected"
			group="Preferences"
			accept="false,true"
			desc="Close the playlist when a song is selected. (some letters might be turned back to mta controls too early (i.e exit vehicle F)"
		/>

		<setting name="*playlist_close_delay" value="500"
			friendlyname="Playlist Close delay"
			group="Preferences"
			accept="0-1000"
			desc="Used if 'Close playlist when selected' is set to true. The delay on closing the playlist when selecting song (not pressing 0 again). In milliseconds (1 sec = 1000 ms)"
		/>
	</settings>

</meta>

local localPlayer = getLocalPlayer()


function constructNumbersFromLetters()
	playlistNumbers = {}
	for number, letter in ipairs (playlistLetters) do
		playlistNumbers[letter] = number
	end
end


function getLocalPlaylist()
	local theSongs = {}
	local xml = xmlLoadFile(":radio3d_playlist/playlist.xml")
	if xml then
		local i = 0
		while (xmlFindChild(xml, "song", i)) do 
			local filename = xmlNodeGetAttribute(xmlFindChild(xml, "song", i), "filename")			
			table.insert(theSongs, ":radio3d_playlist/music/"..filename)
			i = i + 1;
		end
	xmlUnloadFile(xml)    
	end
	
	return theSongs
end


function createPlaylist()	
	playlist = {}
	
	-- 1. Fill the playlist with placeholders
	for number, letter in ipairs (playlistLetters) do
		local filePath = "music/placeholder.mp3"
		-- local songTitle = "Placeholder"
		local songTitle = settings.placeholder
		-- local songTitle = string.sub(filePath, 7, -5)
		
		local song = {filePath, songTitle}

	  	table.insert(playlist, song)  	
	end

	-- 2. Change the beginning of playlist to local playlist
	local localPlaylist = getLocalPlaylist()
	
	for key, filePath in pairs(localPlaylist) do
	 	local songTitle = string.sub(filePath, 25, -5)
	 	local song = {filePath, songTitle}
		playlist[key] = song
	end
	
end



function guiCreateSizedLabel(theText, parent)
	theLabel = guiCreateLabel(0, 0, 0, 0, theText, false, parent)
	guiSetLabelSize(theLabel)
	return theLabel
end

function initMusicSelectorMenu()

	labelSongs = {}
	
	local screenX, screenY = guiGetScreenSize()

	local musicSelectorMenuTitle = settings.playListTitle
	
	musicSelectorMenuX = screenX - musicSelectorMenuWidth - 20
	musicSelectorMenuY = 140	

	musicSelectorMenu = guiCreateWindow(musicSelectorMenuX, musicSelectorMenuY, musicSelectorMenuWidth, 0, musicSelectorMenuTitle, false)

	guiWindowSetMovable(musicSelectorMenu, false)
	guiWindowSetSizable(musicSelectorMenu, false)
	
	disableLabel = guiCreateSizedLabel("x. "..settings.noMusic, musicSelectorMenu)
	closeLabel = guiCreateSizedLabel(settings.closePlaylistDesc)
end

function guiSetLabelSize(theLabel)
	local labelHeight = guiLabelGetFontHeight(theLabel)
    local labelWidth = guiLabelGetTextExtent(theLabel)
    guiSetSize(theLabel, labelWidth, labelHeight, false)
end

function populateMusicSelectorMenu()
	local y = 20	
	local x_offset = 10
	
	for number, song in ipairs (playlist) do              
        local songTitle = song[2]        
        local labelLetter = playlistLetters[number]
        local label = guiCreateLabel(x_offset, y, 0, 0, labelLetter..". "..songTitle, false, musicSelectorMenu)
        local labelHeight = guiLabelGetFontHeight(label)
        guiSetLabelSize(label)
        y = y + labelHeight		
		guiLabelSetColor(label, 255, 255, 255)	
		labelSongs[number] = label	
	end
	
	guiSetPosition(disableLabel, x_offset, y, false)
	    
    local windowHeight = 40+y
	guiSetSize(musicSelectorMenu, musicSelectorMenuWidth, windowHeight, false)
	
	guiSetPosition(closeLabel, musicSelectorMenuX+x_offset, musicSelectorMenuY+windowHeight, false)
end

function drawMusicSelectorMenu()
	initMusicSelectorMenu()
	populateMusicSelectorMenu()

	guiSetVisible(musicSelectorMenu, false)
	guiSetVisible(closeLabel, false)
	bindKey("0", "down", musicSelector)
end

function resetLabels()

	for number, label in ipairs (labelSongs) do                      
        guiSetVisible(label, false)
        label = nil	
	end

	labelSongs = {}	
end

function updateMusicSelectorMenu() 
	resetLabels()	
	
	populateMusicSelectorMenu()
end

function updatePlaylist()	
	createPlaylist()
	updateMusicSelectorMenu()
end


function bindAPlaylistKey(letter)
	bindKey(letter, "down", switchSong)	
end


function unbindAPlaylistKey(letter)
	unbindKey(letter, "down", switchSong)
end


function bindPlaylistKeys()
	toggleAllControls(false)
	
	for number, letter in ipairs (playlistLetters) do
		bindAPlaylistKey(letter)
	end
	
	bindKey("x", "down", stopSong)
end


function unbindPlaylistKeys()
	toggleAllControls(true)

	for number, letter in ipairs (playlistLetters) do
		unbindAPlaylistKey(letter)
	end
	
	unbindKey("x", "down", stopSong)
end


function showMusicSelectorMenu()
	lastVehicle = getPedOccupiedVehicle(getLocalPlayer())
	local selectedSongID = getElementData(lastVehicle, "song_id")

	if selectedSongID ~= false then
		guiLabelSetColor(labelSongs[selectedSongID], 255, 255, 0)
		activeSongCache = selectedSongID
	end

	guiSetVisible(musicSelectorMenu, true)
	guiSetVisible(closeLabel, true)
	bindPlaylistKeys()
end


function hideMusicSelectorMenu()
	guiSetVisible(musicSelectorMenu, false)
	guiSetVisible(closeLabel, false)
	if activeSongCache then
		guiLabelSetColor(labelSongs[activeSongCache], 255, 255, 255)
	end
	unbindPlaylistKeys()
end


function getIDFromLetter(letter)
	return playlistNumbers[letter]
end


-- set current song to selected one (and switch the coloured label if needed)
function switchSong(newLetter, keyState)
	local newSongID = getIDFromLetter(newLetter)
	if playlist[newSongID] ~= nil then
		selectorStopSong()
		guiLabelSetColor(labelSongs[newSongID], 255, 255, 0)
		activeSongCache = newSongID
		PlayerStartSong(newSongID)
		if settings.closeOnSelection then
            -- if hideMusicSelectorMenu() is called too early some letters might be turned to mta controls too early (i.e exit vehicle F)
			setTimer(hideMusicSelectorMenu, settings.playlistCloseDelay, 1)
		end
	end
end


function selectorStopSong()
	if lastVehicle ~= nil then
		local selectedSongID = getElementData(lastVehicle, "song_id")
		if selectedSongID ~= false then
    		guiLabelSetColor(labelSongs[selectedSongID], 255, 255, 255)
			PlayerStopSong(lastVehicle)
		end
	end
end


function stopSong(letter, keyState)
	selectorStopSong()
	hideMusicSelectorMenu()
end


-- does player drive the vehicle (s)he is in at the time
function isPlayerVehicleDriver()
	local playerVehicle = getPedOccupiedVehicle(localPlayer)
	local playerVehicleDriver = getVehicleOccupant(playerVehicle)

	return (localPlayer == playerVehicleDriver)
end


-- toggle dialog with 0
function musicSelector()
	if isPedInVehicle(localPlayer) and isPlayerVehicleDriver() then
		if guiGetVisible(musicSelectorMenu) then
			hideMusicSelectorMenu()
		else
			showMusicSelectorMenu()
		end
	end
end


function saveVehicle(thePlayer, theSeat)
	lastVehicle = getPedOccupiedVehicle(localPlayer)
end


function selectorStopSongAndHideMusicSelectorMenu(thePlayer, theSeat)
	if (thePlayer == localPlayer) and (theSeat == 0) then
		if settings.soundRadioOffOnVehicleExit then
			selectorStopSong() -- turn off radio, when exiting vehicle
		end
		hideMusicSelectorMenu()
	end
end


function updateLastVehicle()
	lastVehicle = getPedOccupiedVehicle(localPlayer)
end


function table.size(tab)
    local length = 0
    for _ in pairs(tab) do length = length + 1 end
    return length
end


function PlayerStopSong()
	setElementData(lastVehicle, "song_id", false)
	setElementData(lastVehicle, "song_start_time", false)
end


function PlayerStartSong(songID)
    local playerVehicle = getPedOccupiedVehicle(getLocalPlayer())

	if playerVehicle then
		-- It syncs with server automatically
		setElementData(playerVehicle, "song_id", songID)
		setElementData(playerVehicle, "song_start_time", getTickCount())
	end
end


function initiate3DSound(songId, x, y, z, songStartTime, ownVehicle)
	-- loop = true

	local loop = settings.loop
	
	songId = tonumber(songId)

	local song = playlist[songId]
	
	if song == nil then
		outputDebugString("initiate3DSound: song is nil")
		return false
	else
		if song[1] == nil then
			outputDebugString("initiate3DSound: song[1] is nil")
			return false
		end
	end
	
	-- Seems there some timing problems?
	-- Should add a little timer here?
	-- Usually only first time
	local filepath = song[1]

    local volume = settings.soundOutsideVolumeOnFoot

	if isPedInVehicle(localPlayer) then
		volume = settings.soundOutsideVolumeInVehicle
	else
		-- on foot
		volume = settings.soundOutsideVolumeOnFoot
	end

	-- full volume for own vehicle (no need to be a driver)
	if (ownVehicle ~= nil) and (ownVehicle ~= false) then
		volume = settings.soundOwnVehicleVolume
	end

	outputDebugString("Starting playSound3D")
	local sound3d = playSound3D(filepath, x, y, z, loop)

	setSoundVolume(sound3d, volume)
	setSoundMinDistance(sound3d, settings.soundMinDistance)
	setSoundMaxDistance(sound3d, settings.soundMaxDistance)

    -- if sound initiated has been started before creation for this player
	if (songStartTime ~= nil) and (songStartTime > 0) then
		local currentTickCount = getTickCount()
		local seekPosition = getTickCount() - songStartTime

		local soundLength = getSoundLength(sound3d)

		if (soundLength ~= false) and (soundLength > 0) then
			timesLooped = seekPosition/soundLength
			if timesLooped >= 1 then
				seekPosition = seekPosition%soundLength
			end
		end

		setSoundPosition(sound3d, seekPosition)
	end

	return sound3d
end

-- if client is in that vehicle
function inVehicle(theVehicle)
	if theVehicle ~= nil then
		myVehicle = getPedOccupiedVehicle(getLocalPlayer())
		if myVehicle == theVehicle then
			return true
		end
	end

	return false
end


-- toElement is a vehicle for example
function attach3DSound(toElement, songId, songStartTime)
	local x, y, z = getElementPosition(toElement)
	if songStartTime == nil then
		songStartTime = 0
	end

	-- set vehicle you're in louder than others (really others are trimmed down)
	local soundElement = initiate3DSound(songId, x, y, z, songStartTime, inVehicle(toElement))

	sound3DElements[toElement] = soundElement
end


-- source is an element, a vehicle for example
function detach3DSound(fromElement)
	if (sound3DElements[fromElement] ~= nil) then
		local soundElement = sound3DElements[fromElement]
		stopSound(soundElement)
		sound3DElements[fromElement] = nil
	end
end


function updateSong(dataName, oldValue)
	if getElementType(source) == "vehicle" and dataName == "song_id" then
		-- oldValue will probably always show as false, because we stopped the music before starting new one
		-- this means we need to make new 3dsound object
		if (oldValue ~= nil) and (oldValue ~= false) then
			if (getElementData(source, dataName)) then
				attach3DSound(source, getElementData(source, dataName))
				-- if this msg is get sth is wrong, because replacing song should stop old one
				-- outputDebugString("CLIENT: set song from "..oldValue.." to "..getElementData(source, dataName).." for "..getVehicleName(source))
			else
				detach3DSound(source)
				-- outputDebugString("CLIENT: turned off song for "..getVehicleName(source))
			end
		else
			attach3DSound(source, getElementData(source, dataName))
			-- outputDebugString("CLIENT: set song from nothing to "..getElementData(source, dataName).." for "..getVehicleName(source))
		end
	end
end

function removeSongFromElement()
	if getElementType(source) == "vehicle" then
		-- detach3DSound checks itself if there is such element afterall
		detach3DSound(source)
	end
end

function rePositionSound()
	-- this needs to be with VERY good performance
	if sound3DElements ~= nil then
		for element, sound in pairs(sound3DElements) do
			local x, y, z = getElementPosition(element)
			setElementPosition(sound, x, y, z)
		end
	end
end


function showSound3DElementsSize(commandName)
	outputChatBox(table.size(sound3DElements))
end


-- when a client joins server, start music elements
function initiate3DSoundsOnConnect()
	local vehicles = getElementsByType("vehicle")

	-- on client render might be needed

	for theKey, theVehicle in ipairs(vehicles) do
		local songId = getElementData(theVehicle, "song_id")
		-- if song is attached to element (song_id is set, not false)
		if songId then
			local songStartTime = getElementData(theVehicle, "song_start_time")
			attach3DSound(theVehicle, songId, songStartTime)
		end
	end
end

function cancelRadioEvent(stationID)
    cancelEvent()
end

function disableRadio()
    setRadioChannel(13)
end


-- this helps only when song_id was changed (including turn off and on (not on first connect)
addEventHandler("onClientElementDataChange", getRootElement(), updateSong)
addEventHandler("onClientElementDestroy", getRootElement(), removeSongFromElement)

addEventHandler("onClientPreRender", getRootElement(),
	function()
		rePositionSound()
	end
)



addEventHandler("onClientPlayerVehicleEnter", getRootElement(),
	function()
		updateLastVehicle()
		-- when entering vehicle set all volumes to inVehicle
		-- except maybe a song is playing in here already, then put this to maximum

        if table.size(sound3DElements) > 0 then
			for element, sound in pairs(sound3DElements) do
				if inVehicle(element) then
					-- outputDebugString("my car playing ".. settings.soundOwnVehicleVolume)
					setSoundVolume(sound, settings.soundOwnVehicleVolume)
				else
					-- outputDebugString("not my car playing "..settings.soundOutsideVolumeInVehicle)
					setSoundVolume(sound, settings.soundOutsideVolumeInVehicle)
				end
			end
		end

		-- try to set radio to none (that's all, let them change it if wanted)
		disableRadio()

	end
)

addEventHandler("onClientVehicleStartExit", getRootElement(), saveVehicle)

addEventHandler("onClientVehicleExit", getRootElement(),
	function(thePlayer, theSeat)

	-- when exiting vehicle set all volumes to onFoot
	if table.size(sound3DElements) > 0 then
		for element, sound in pairs(sound3DElements) do
			setSoundVolume(sound, settings.soundOutsideVolumeOnFoot)
			-- outputDebugString("On foot, all to "..settings.soundOutsideVolumeOnFoot)
		end
	end

	selectorStopSongAndHideMusicSelectorMenu(thePlayer, theSeat)

	end
)

-- addEventHandler("onClientPlayerRadioSwitch", getRootElement(), cancelRadioEvent)


addCommandHandler("songs", showSound3DElementsSize)

addCommandHandler("updateplaylist", updatePlaylist)

addEvent("onSendRadio3dSettings", true)

-- Wait for server to return settings
addEventHandler("onSendRadio3dSettings", localPlayer,
	function(newSettings)
    	if newSettings == nil then
			triggerServerEvent("onRadio3dClientScriptLoaded", localPlayer)
		else
			settings = newSettings
			
			-- outputChatBox("settings: "..tostring(settings))
			-- local table of sound elements
			sound3DElements = {}

			playlistLetters = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}

			constructNumbersFromLetters()		
		
			createPlaylist()
		
			musicSelectorMenuWidth = 240
    		drawMusicSelectorMenu()
    	
			initiate3DSoundsOnConnect()
			-- outputChatBox("done")
		end
	end
)		

addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()),
	function()	
		triggerServerEvent("onRadio3dClientScriptLoaded", localPlayer)
	end
)

		
addEvent("onGamemodeMapStop", true)

addEventHandler("onGamemodeMapStop", getRootElement(),
	function()
		-- outputChatBox("kliendil ka nyyd olemas")
		
		--local vehicles = getElementsByType("vehicle")

		-- Remove all songs from elements
		--[[
		for theKey, theVehicle in ipairs(vehicles) do
			setElementData(theVehicle, "song_id", false)
			setElementData(theVehicle, "song_start_time", false)			
		end
		]]
	end
)        


-- For exported function
function changeRadio3dSetting(settingName, newValue)
	if settingName == nil then
		return false
	end
	
	if type(settingName) ~= "string" then
		return false
	end

	if newValue == nil then
		return false
	end
	
	if settings == nil then
		settings = {}
	end
	
	settings[settingName] = newValue
end


addEvent("onChangeRadio3dSetting", true)
addEventHandler("onChangeRadio3dSetting", localPlayer, changeRadio3dSetting)

server_player.lua

local root = getRootElement()

-- get string or default
function getString(var,default)
    local result = get(var)
    if not result then
        return default
    end
    return tostring(result)
end

-- get number or default
function getNumber(var,default)
    local result = get(var)
    if not result then
        return default
    end
    return tonumber(result)
end

-- get true or false or default
function getBool(var,default)
    local result = get(var)
    if not result then
        return default
    end
    return result == 'true'
end


local settings = {
	soundMinDistance = getNumber('radio3d.sound3d_min_distance', 8),
	soundMaxDistance = getNumber('radio3d.sound3d_max_distance', 15),
	soundOutsideVolumeInVehicle = getNumber('radio3d.sound3d_outside_volume_vehicle', 50) / 100,
	soundOutsideVolumeOnFoot = getNumber('radio3d.sound3d_outside_volume_foot', 70) / 100,
	soundOwnVehicleVolume = getNumber('radio3d.sound3d_own_vehicle_volume', 100) / 100,
	soundRadioOffOnVehicleExit = getBool('radio3d.sound3d_radio_off_on_vehicle_exit', false),
	soundRadioOffOnBlowUp = getBool('radio3d.sound3d_radio_off_on_blow_up', true),
	playListTitle = getString('radio3d.playlist_title', "Плеер"),
	closePlaylistDesc = getString('radio3d.close_playlist_desc', "Нажмите 0 для закрытие плеера"),
	noMusic = getString('radio3d.no_music', "No music"),
	placeholder = getString('radio3d.placeholder', "Placeholder"),
	loop = getBool('radio3d.loop', true),
	closeOnSelection = getBool('radio3d.close_on_selection', true),
	playlistCloseDelay = getNumber('radio3d.playlist_close_delay', 500)
}

addEvent("onRadio3dClientScriptLoaded", true)	
addEventHandler("onRadio3dClientScriptLoaded", getRootElement(),
	function()
		-- outputChatBox("settings: "..tostring(settings))
		triggerClientEvent(source, "onSendRadio3dSettings", getRootElement(), settings)
	end
)


-- function removeSongOnExplode(theVehicle)
-- 	setElementData(theVehicle, "tempdata.originalnick", sPlayerNickname)
-- end

-- addEventHandler("onVehicleExplode", getRootElement(), removeSongOnExplode)


function removeAllSounds()
    local vehicles = getElementsByType("vehicle")

	for theKey, theVehicle in ipairs(vehicles) do
		-- sometimes real elements are already deleted (i.e onGamemodeMapStop), so extra check
		if isElement(theVehicle) then
			local songId = getElementData(theVehicle, "song_id")
			-- if song is attached to element (song_id is set, not false (or nil))
			if songId then
            	setElementData(theVehicle, "song_id", false)
				setElementData(theVehicle, "song_start_time", false)
			end
		end
	end
end


function onCurrentResourceStop(theResource)
	removeAllSounds()
end

function removeSongOnGamemodeMapStop(gamemode)
	-- outputChatBox("gamemode stopped")
	triggerClientEvent("onGamemodeMapStop", getRootElement())

	removeAllSounds()
end

-- function removeSongOnGamemodeMapStart(gamemode)
-- 	outputChatBox("gamemode started")
-- end

function removeSongFromElement(theElement)
	local songId = getElementData(theElement, "song_id")
	if songId then
    	setElementData(theElement, "song_id", false)
		setElementData(theElement, "song_start_time", false)
	end
end

function removeSongOnExplode()
	removeSongFromElement(source)
end

if settings.soundRadioOffOnBlowUp then
	addEventHandler("onVehicleExplode", getRootElement(), removeSongOnExplode)
end

addEventHandler("onResourceStop", getResourceRootElement(), onCurrentResourceStop)

addEventHandler("onGamemodeMapStop", getRootElement(), removeSongOnGamemodeMapStop)
-- addEventHandler("onGamemodeMapStart", getRootElement(), removeSongOnGamemodeMapStart)



-- Export
function changeSetting(triggerFor, settingName, newValue)
	triggerClientEvent(triggerFor, "onChangeRadio3dSetting", root, settingName, newValue)
end

 


radio3d_client.lua

Edited by Marvik
Add spoilers
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...