/*
Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

/**
 * @fileOverview Defines the {@link CKEDITOR.lang} object, for the
 * Serbian (Latin) language.
 */

/**#@+
   @type String
   @example
*/

/**
 * Constains the dictionary of language entries.
 * @namespace
 */
CKEDITOR.lang['sr-latn'] =
{
	/**
	 * The language reading direction. Possible values are "rtl" for
	 * Right-To-Left languages (like Arabic) and "ltr" for Left-To-Right
	 * languages (like English).
	 * @default 'ltr'
	 */
	dir : 'ltr',

	/*
	 * Screenreader titles. Please note that screenreaders are not always capable
	 * of reading non-English words. So be careful while translating it.
	 */
	editorTitle : 'Rich text editor, %1, press ALT 0 for help.', // MISSING

	// ARIA descriptions.
	toolbar	: 'Toolbar', // MISSING
	editor	: 'Rich Text Editor', // MISSING

	// Toolbar buttons without dialogs.
	source			: 'Kôd',
	newPage			: 'Nova stranica',
	save			: 'Sačuvaj',
	preview			: 'Izgled stranice',
	cut				: 'Iseci',
	copy			: 'Kopiraj',
	paste			: 'Zalepi',
	print			: 'Štampa',
	underline		: 'Podvučeno',
	bold			: 'Podebljano',
	italic			: 'Kurziv',
	selectAll		: 'Označi sve',
	removeFormat	: 'Ukloni formatiranje',
	strike			: 'Precrtano',
	subscript		: 'Indeks',
	superscript		: 'Stepen',
	horizontalrule	: 'Unesi horizontalnu liniju',
	pagebreak		: 'Insert Page Break for Printing', // MISSING
	unlink			: 'Ukloni link',
	undo			: 'Poni�ti akciju',
	redo			: 'Ponovi akciju',

	// Common messages and labels.
	common :
	{
		browseServer	: 'Pretraži server',
		url				: 'URL',
		protocol		: 'Protokol',
		upload			: 'Pošalji',
		uploadSubmit	: 'Pošalji na server',
		image			: 'Slika',
		flash			: 'Fleš',
		form			: 'Forma',
		checkbox		: 'Polje za potvrdu',
		radio			: 'Radio-dugme',
		textField		: 'Tekstualno polje',
		textarea		: 'Zona teksta',
		hiddenField		: 'Skriveno polje',
		button			: 'Dugme',
		select			: 'Izborno polje',
		imageButton		: 'Dugme sa slikom',
		notSet			: '<nije postavljeno>',
		id				: 'Id',
		name			: 'Naziv',
		langDir			: 'Smer jezika',
		langDirLtr		: 'S leva na desno (LTR)',
		langDirRtl		: 'S desna na levo (RTL)',
		langCode		: 'Kôd jezika',
		longDescr		: 'Pun opis URL',
		cssClass		: 'Stylesheet klase',
		advisoryTitle	: 'Advisory naslov',
		cssStyle		: 'Stil',
		ok				: 'OK',
		cancel			: 'Otkaži',
		close			: 'Close', // MISSING
		preview			: 'Preview', // MISSING
		generalTab		: 'General', // MISSING
		advancedTab		: 'Napredni tagovi',
		validateNumberFailed : 'This value is not a number.', // MISSING
		confirmNewPage	: 'Any unsaved changes to this content will be lost. Are you sure you want to load new page?', // MISSING
		confirmCancel	: 'Some of the options have been changed. Are you sure to close the dialog?', // MISSING
		options			: 'Options', // MISSING
		target			: 'Target', // MISSING
		targetNew		: 'New Window (_blank)', // MISSING
		targetTop		: 'Topmost Window (_top)', // MISSING
		targetSelf		: 'Same Window (_self)', // MISSING
		targetParent	: 'Parent Window (_parent)', // MISSING

		// Put the voice-only part of the label in the span.
		unavailable		: '%1, unavailable' // MISSING
	},

	contextmenu :
	{
		options : 'Context Menu Options' // MISSING
	},

	// Special char dialog.
	specialChar		:
	{
		toolbar		: 'Unesi specijalni karakter',
		title		: 'Odaberite specijalni karakter',
		options : 'Special Character Options' // MISSING
	},

	// Link dialog.
	link :
	{
		toolbar		: 'Unesi/izmeni link',
		other 		: '<остало>',
		menu		: 'Izmeni link',
		title		: 'Link',
		info		: 'Link Info',
		target		: 'Meta',
		upload		: 'Pošalji',
		advanced	: 'Napredni tagovi',
		type		: 'Vrsta linka',
		toUrl		: 'URL', // MISSING
		toAnchor	: 'Sidro na ovoj stranici',
		toEmail		: 'E-Mail',
		targetFrame		: '<okvir>',
		targetPopup		: '<popup prozor>',
		targetFrameName	: 'Naziv odredišnog frejma',
		targetPopupName	: 'Naziv popup prozora',
		popupFeatures	: 'Mogućnosti popup prozora',
		popupResizable	: 'Resizable', // MISSING
		popupStatusBar	: 'Statusna linija',
		popupLocationBar: 'Lokacija',
		popupToolbar	: 'Toolbar',
		popupMenuBar	: 'Kontekstni meni',
		popupFullScreen	: 'Prikaz preko celog ekrana (IE)',
		popupScrollBars	: 'Scroll bar',
		popupDependent	: 'Zavisno (Netscape)',
		popupWidth		: 'Širina',
		popupLeft		: 'Od leve ivice ekrana (px)',
		popupHeight		: 'Visina',
		popupTop		: 'Od vrha ekrana (px)',
		id				: 'Id', // MISSING
		langDir			: 'Smer jezika',
		langDirLTR		: 'S leva na desno (LTR)',
		langDirRTL		: 'S desna na levo (RTL)',
		acccessKey		: 'Pristupni taster',
		name			: 'Naziv',
		langCode		: 'Smer jezika',
		tabIndex		: 'Tab indeks',
		advisoryTitle	: 'Advisory naslov',
		advisoryContentType	: 'Advisory vrsta sadržaja',
		cssClasses		: 'Stylesheet klase',
		charset			: 'Linked Resource Charset',
		styles			: 'Stil',
		selectAnchor	: 'Odaberi sidro',
		anchorName		: 'Po nazivu sidra',
		anchorId		: 'Po Id-ju elementa',
		emailAddress	: 'E-Mail adresa',
		emailSubject	: 'Naslov',
		emailBody		: 'Sadržaj poruke',
		noAnchors		: '(Nema dostupnih sidra)',
		noUrl			: 'Unesite URL linka',
		noEmail			: 'Otkucajte adresu elektronske pote'
	},

	// Anchor dialog
	anchor :
	{
		toolbar		: 'Unesi/izmeni sidro',
		menu		: 'Osobine sidra',
		title		: 'Osobine sidra',
		name		: 'Ime sidra',
		errorName	: 'Unesite ime sidra'
	},

	// Find And Replace Dialog
	findAndReplace :
	{
		title				: 'Find and Replace', // MISSING
		find				: 'Pretraga',
		replace				: 'Zamena',
		findWhat			: 'Pronadi:',
		replaceWith			: 'Zameni sa:',
		notFoundMsg			: 'Traženi tekst nije pronađen.',
		matchCase			: 'Razlikuj mala i velika slova',
		matchWord			: 'Uporedi cele reci',
		matchCyclic			: 'Match cyclic', // MISSING
		replaceAll			: 'Zameni sve',
		replaceSuccessMsg	: '%1 occurrence(s) replaced.' // MISSING
	},

	// Table Dialog
	table :
	{
		toolbar		: 'Tabela',
		title		: 'Osobine tabele',
		menu		: 'Osobine tabele',
		deleteTable	: 'Delete Table', // MISSING
		rows		: 'Redova',
		columns		: 'Kolona',
		border		: 'Veličina okvira',
		align		: 'Ravnanje',
		alignLeft	: 'Levo',
		alignCenter	: 'Sredina',
		alignRight	: 'Desno',
		width		: 'Širina',
		widthPx		: 'piksela',
		widthPc		: 'procenata',
		widthUnit	: 'width unit', // MISSING
		height		: 'Visina',
		cellSpace	: 'Ćelijski prostor',
		cellPad		: 'Razmak ćelija',
		caption		: 'Naslov tabele',
		summary		: 'Summary', // MISSING
		headers		: 'Headers', // MISSING
		headersNone		: 'None', // MISSING
		headersColumn	: 'First column', // MISSING
		headersRow		: 'First Row', // MISSING
		headersBoth		: 'Both', // MISSING
		invalidRows		: 'Number of rows must be a number greater than 0.', // MISSING
		invalidCols		: 'Number of columns must be a number greater than 0.', // MISSING
		invalidBorder	: 'Border size must be a number.', // MISSING
		invalidWidth	: 'Table width must be a number.', // MISSING
		invalidHeight	: 'Table height must be a number.', // MISSING
		invalidCellSpacing	: 'Cell spacing must be a number.', // MISSING
		invalidCellPadding	: 'Cell padding must be a number.', // MISSING

		cell :
		{
			menu			: 'Cell', // MISSING
			insertBefore	: 'Insert Cell Before', // MISSING
			insertAfter		: 'Insert Cell After', // MISSING
			deleteCell		: 'Obriši ćelije',
			merge			: 'Spoj celije',
			mergeRight		: 'Merge Right', // MISSING
			mergeDown		: 'Merge Down', // MISSING
			splitHorizontal	: 'Split Cell Horizontally', // MISSING
			splitVertical	: 'Split Cell Vertically', // MISSING
			title			: 'Cell Properties', // MISSING
			cellType		: 'Cell Type', // MISSING
			rowSpan			: 'Rows Span', // MISSING
			colSpan			: 'Columns Span', // MISSING
			wordWrap		: 'Word Wrap', // MISSING
			hAlign			: 'Horizontal Alignment', // MISSING
			vAlign			: 'Vertical Alignment', // MISSING
			alignTop		: 'Top', // MISSING
			alignMiddle		: 'Middle', // MISSING
			alignBottom		: 'Bottom', // MISSING
			alignBaseline	: 'Baseline', // MISSING
			bgColor			: 'Background Color', // MISSING
			borderColor		: 'Border Color', // MISSING
			data			: 'Data', // MISSING
			header			: 'Header', // MISSING
			yes				: 'Yes', // MISSING
			no				: 'No', // MISSING
			invalidWidth	: 'Cell width must be a number.', // MISSING
			invalidHeight	: 'Cell height must be a number.', // MISSING
			invalidRowSpan	: 'Rows span must be a whole number.', // MISSING
			invalidColSpan	: 'Columns span must be a whole number.', // MISSING
			chooseColor		: 'Choose' // MISSING
		},

		row :
		{
			menu			: 'Row', // MISSING
			insertBefore	: 'Insert Row Before', // MISSING
			insertAfter		: 'Insert Row After', // MISSING
			deleteRow		: 'Obriši redove'
		},

		column :
		{
			menu			: 'Column', // MISSING
			insertBefore	: 'Insert Column Before', // MISSING
			insertAfter		: 'Insert Column After', // MISSING
			deleteColumn	: 'Obriši kolone'
		}
	},

	// Button Dialog.
	button :
	{
		title		: 'Osobine dugmeta',
		text		: 'Tekst (vrednost)',
		type		: 'Tip',
		typeBtn		: 'Button', // MISSING
		typeSbm		: 'Submit', // MISSING
		typeRst		: 'Reset' // MISSING
	},

	// Checkbox and Radio Button Dialogs.
	checkboxAndRadio :
	{
		checkboxTitle : 'Osobine polja za potvrdu',
		radioTitle	: 'Osobine radio-dugmeta',
		value		: 'Vrednost',
		selected	: 'Označeno'
	},

	// Form Dialog.
	form :
	{
		title		: 'Osobine forme',
		menu		: 'Osobine forme',
		action		: 'Akcija',
		method		: 'Metoda',
		encoding	: 'Encoding' // MISSING
	},

	// Select Field Dialog.
	select :
	{
		title		: 'Osobine izbornog polja',
		selectInfo	: 'Info',
		opAvail		: 'Dostupne opcije',
		value		: 'Vrednost',
		size		: 'Veličina',
		lines		: 'linija',
		chkMulti	: 'Dozvoli višestruku selekciju',
		opText		: 'Tekst',
		opValue		: 'Vrednost',
		btnAdd		: 'Dodaj',
		btnModify	: 'Izmeni',
		btnUp		: 'Gore',
		btnDown		: 'Dole',
		btnSetValue : 'Podesi kao označenu vrednost',
		btnDelete	: 'Obriši'
	},

	// Textarea Dialog.
	textarea :
	{
		title		: 'Osobine zone teksta',
		cols		: 'Broj kolona',
		rows		: 'Broj redova'
	},

	// Text Field Dialog.
	textfield :
	{
		title		: 'Osobine tekstualnog polja',
		name		: 'Naziv',
		value		: 'Vrednost',
		charWidth	: 'Širina (karaktera)',
		maxChars	: 'Maksimalno karaktera',
		type		: 'Tip',
		typeText	: 'Tekst',
		typePass	: 'Lozinka'
	},

	// Hidden Field Dialog.
	hidden :
	{
		title	: 'Osobine skrivenog polja',
		name	: 'Naziv',
		value	: 'Vrednost'
	},

	// Image Dialog.
	image :
	{
		title		: 'Osobine slika',
		titleButton	: 'Osobine dugmeta sa slikom',
		menu		: 'Osobine slika',
		infoTab		: 'Info slike',
		btnUpload	: 'Pošalji na server',
		upload		: 'Pošalji',
		alt			: 'Alternativni tekst',
		width		: 'Širina',
		height		: 'Visina',
		lockRatio	: 'Zaključaj odnos',
		unlockRatio	: 'Unlock Ratio', // MISSING
		resetSize	: 'Resetuj veličinu',
		border		: 'Okvir',
		hSpace		: 'HSpace',
		vSpace		: 'VSpace',
		align		: 'Ravnanje',
		alignLeft	: 'Levo',
		alignRight	: 'Desno',
		alertUrl	: 'Unesite URL slike',
		linkTab		: 'Link',
		button2Img	: 'Do you want to transform the selected image button on a simple image?', // MISSING
		img2Button	: 'Do you want to transform the selected image on a image button?', // MISSING
		urlMissing	: 'Image source URL is missing.', // MISSING
		validateWidth	: 'Width must be a whole number.', // MISSING
		validateHeight	: 'Height must be a whole number.', // MISSING
		validateBorder	: 'Border must be a whole number.', // MISSING
		validateHSpace	: 'HSpace must be a whole number.', // MISSING
		validateVSpace	: 'VSpace must be a whole number.' // MISSING
	},

	// Flash Dialog
	flash :
	{
		properties		: 'Osobine fleša',
		propertiesTab	: 'Properties', // MISSING
		title			: 'Osobine fleša',
		chkPlay			: 'Automatski start',
		chkLoop			: 'Ponavljaj',
		chkMenu			: 'Uključi fleš meni',
		chkFull			: 'Allow Fullscreen', // MISSING
 		scale			: 'Skaliraj',
		scaleAll		: 'Prikaži sve',
		scaleNoBorder	: 'Bez ivice',
		scaleFit		: 'Popuni površinu',
		access			: 'Script Access', // MISSING
		accessAlways	: 'Always', // MISSING
		accessSameDomain: 'Same domain', // MISSING
		accessNever		: 'Never', // MISSING
		align			: 'Ravnanje',
		alignLeft		: 'Levo',
		alignAbsBottom	: 'Abs dole',
		alignAbsMiddle	: 'Abs sredina',
		alignBaseline	: 'Bazno',
		alignBottom		: 'Dole',
		alignMiddle		: 'Sredina',
		alignRight		: 'Desno',
		alignTextTop	: 'Vrh teksta',
		alignTop		: 'Vrh',
		quality			: 'Quality', // MISSING
		qualityBest		: 'Best', // MISSING
		qualityHigh		: 'High', // MISSING
		qualityAutoHigh	: 'Auto High', // MISSING
		qualityMedium	: 'Medium', // MISSING
		qualityAutoLow	: 'Auto Low', // MISSING
		qualityLow		: 'Low', // MISSING
		windowModeWindow: 'Window', // MISSING
		windowModeOpaque: 'Opaque', // MISSING
		windowModeTransparent : 'Transparent', // MISSING
		windowMode		: 'Window mode', // MISSING
		flashvars		: 'Variables for Flash', // MISSING
		bgcolor			: 'Boja pozadine',
		width			: 'Širina',
		height			: 'Visina',
		hSpace			: 'HSpace',
		vSpace			: 'VSpace',
		validateSrc		: 'Unesite URL linka',
		validateWidth	: 'Width must be a number.', // MISSING
		validateHeight	: 'Height must be a number.', // MISSING
		validateHSpace	: 'HSpace must be a number.', // MISSING
		validateVSpace	: 'VSpace must be a number.' // MISSING
	},

	// Speller Pages Dialog
	spellCheck :
	{
		toolbar			: 'Proveri spelovanje',
		title			: 'Spell Check', // MISSING
		notAvailable	: 'Sorry, but service is unavailable now.', // MISSING
		errorLoading	: 'Error loading application service host: %s.', // MISSING
		notInDic		: 'Nije u rečniku',
		changeTo		: 'Izmeni',
		btnIgnore		: 'Ignoriši',
		btnIgnoreAll	: 'Ignoriši sve',
		btnReplace		: 'Zameni',
		btnReplaceAll	: 'Zameni sve',
		btnUndo			: 'Vrati akciju',
		noSuggestions	: '- Bez sugestija -',
		progress		: 'Provera spelovanja u toku...',
		noMispell		: 'Provera spelovanja završena: greške nisu pronadene',
		noChanges		: 'Provera spelovanja završena: Nije izmenjena nijedna rec',
		oneChange		: 'Provera spelovanja završena: Izmenjena je jedna reč',
		manyChanges		: 'Provera spelovanja završena: %1 reč(i) je izmenjeno',
		ieSpellDownload	: 'Provera spelovanja nije instalirana. Da li želite da je skinete sa Interneta?'
	},

	smiley :
	{
		toolbar	: 'Smajli',
		title	: 'Unesi smajlija',
		options : 'Smiley Options' // MISSING
	},

	elementsPath :
	{
		eleLabel : 'Elements path', // MISSING
		eleTitle : '%1 element' // MISSING
	},

	numberedlist	: 'Nabrojiva lista',
	bulletedlist	: 'Nenabrojiva lista',
	indent			: 'Uvećaj levu marginu',
	outdent			: 'Smanji levu marginu',

	justify :
	{
		left	: 'Levo ravnanje',
		center	: 'Centriran tekst',
		right	: 'Desno ravnanje',
		block	: 'Obostrano ravnanje'
	},

	blockquote : 'Block Quote', // MISSING

	clipboard :
	{
		title		: 'Zalepi',
		cutError	: 'Sigurnosna podešavanja Vašeg pretraživača ne dozvoljavaju operacije automatskog isecanja teksta. Molimo Vas da koristite prečicu sa tastature (Ctrl/Cmd+X).',
		copyError	: 'Sigurnosna podešavanja Vašeg pretraživača ne dozvoljavaju operacije automatskog kopiranja teksta. Molimo Vas da koristite prečicu sa tastature (Ctrl/Cmd+C).',
		pasteMsg	: 'Molimo Vas da zalepite unutar donje povrine koristeći tastaturnu prečicu (Ctrl/Cmd+V) i da pritisnete OK.',
		securityMsg	: 'Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.', // MISSING
		pasteArea	: 'Paste Area' // MISSING
	},

	pastefromword :
	{
		confirmCleanup	: 'The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?', // MISSING
		toolbar			: 'Zalepi iz Worda',
		title			: 'Zalepi iz Worda',
		error			: 'It was not possible to clean up the pasted data due to an internal error' // MISSING
	},

	pasteText :
	{
		button	: 'Zalepi kao čist tekst',
		title	: 'Zalepi kao čist tekst'
	},

	templates :
	{
		button			: 'Obrasci',
		title			: 'Obrasci za sadržaj',
		options : 'Template Options', // MISSING
		insertOption	: 'Replace actual contents', // MISSING
		selectPromptMsg	: 'Molimo Vas da odaberete obrazac koji ce biti primenjen na stranicu (trenutni sadržaj ce biti obrisan):',
		emptyListMsg	: '(Nema definisanih obrazaca)'
	},

	showBlocks : 'Show Blocks', // MISSING

	stylesCombo :
	{
		label		: 'Stil',
		panelTitle	: 'Formatting Styles', // MISSING
		panelTitle1	: 'Block Styles', // MISSING
		panelTitle2	: 'Inline Styles', // MISSING
		panelTitle3	: 'Object Styles' // MISSING
	},

	format :
	{
		label		: 'Format',
		panelTitle	: 'Format',

		tag_p		: 'Normal',
		tag_pre		: 'Formatirano',
		tag_address	: 'Adresa',
		tag_h1		: 'Naslov 1',
		tag_h2		: 'Naslov 2',
		tag_h3		: 'Naslov 3',
		tag_h4		: 'Naslov 4',
		tag_h5		: 'Naslov 5',
		tag_h6		: 'Naslov 6',
		tag_div		: 'Normal (DIV)' // MISSING
	},

	div :
	{
		title				: 'Create Div Container', // MISSING
		toolbar				: 'Create Div Container', // MISSING
		cssClassInputLabel	: 'Stylesheet Classes', // MISSING
		styleSelectLabel	: 'Style', // MISSING
		IdInputLabel		: 'Id', // MISSING
		languageCodeInputLabel	: ' Language Code', // MISSING
		inlineStyleInputLabel	: 'Inline Style', // MISSING
		advisoryTitleInputLabel	: 'Advisory Title', // MISSING
		langDirLabel		: 'Language Direction', // MISSING
		langDirLTRLabel		: 'Left to Right (LTR)', // MISSING
		langDirRTLLabel		: 'Right to Left (RTL)', // MISSING
		edit				: 'Edit Div', // MISSING
		remove				: 'Remove Div' // MISSING
  	},

	font :
	{
		label		: 'Font',
		voiceLabel	: 'Font', // MISSING
		panelTitle	: 'Font'
	},

	fontSize :
	{
		label		: 'Veličina fonta',
		voiceLabel	: 'Font Size', // MISSING
		panelTitle	: 'Veličina fonta'
	},

	colorButton :
	{
		textColorTitle	: 'Boja teksta',
		bgColorTitle	: 'Boja pozadine',
		panelTitle		: 'Colors', // MISSING
		auto			: 'Automatski',
		more			: 'Više boja...'
	},

	colors :
	{
		'000' : 'Black', // MISSING
		'800000' : 'Maroon', // MISSING
		'8B4513' : 'Saddle Brown', // MISSING
		'2F4F4F' : 'Dark Slate Gray', // MISSING
		'008080' : 'Teal', // MISSING
		'000080' : 'Navy', // MISSING
		'4B0082' : 'Indigo', // MISSING
		'696969' : 'Dim Gray', // MISSING
		'B22222' : 'Fire Brick', // MISSING
		'A52A2A' : 'Brown', // MISSING
		'DAA520' : 'Golden Rod', // MISSING
		'006400' : 'Dark Green', // MISSING
		'40E0D0' : 'Turquoise', // MISSING
		'0000CD' : 'Medium Blue', // MISSING
		'800080' : 'Purple', // MISSING
		'808080' : 'Gray', // MISSING
		'F00' : 'Red', // MISSING
		'FF8C00' : 'Dark Orange', // MISSING
		'FFD700' : 'Gold', // MISSING
		'008000' : 'Green', // MISSING
		'0FF' : 'Cyan', // MISSING
		'00F' : 'Blue', // MISSING
		'EE82EE' : 'Violet', // MISSING
		'A9A9A9' : 'Dark Gray', // MISSING
		'FFA07A' : 'Light Salmon', // MISSING
		'FFA500' : 'Orange', // MISSING
		'FFFF00' : 'Yellow', // MISSING
		'00FF00' : 'Lime', // MISSING
		'AFEEEE' : 'Pale Turquoise', // MISSING
		'ADD8E6' : 'Light Blue', // MISSING
		'DDA0DD' : 'Plum', // MISSING
		'D3D3D3' : 'Light Grey', // MISSING
		'FFF0F5' : 'Lavender Blush', // MISSING
		'FAEBD7' : 'Antique White', // MISSING
		'FFFFE0' : 'Light Yellow', // MISSING
		'F0FFF0' : 'Honeydew', // MISSING
		'F0FFFF' : 'Azure', // MISSING
		'F0F8FF' : 'Alice Blue', // MISSING
		'E6E6FA' : 'Lavender', // MISSING
		'FFF' : 'White' // MISSING
	},

	scayt :
	{
		title			: 'Spell Check As You Type', // MISSING
		enable			: 'Enable SCAYT', // MISSING
		disable			: 'Disable SCAYT', // MISSING
		about			: 'About SCAYT', // MISSING
		toggle			: 'Toggle SCAYT', // MISSING
		options			: 'Options', // MISSING
		langs			: 'Languages', // MISSING
		moreSuggestions	: 'More suggestions', // MISSING
		ignore			: 'Ignore', // MISSING
		ignoreAll		: 'Ignore All', // MISSING
		addWord			: 'Add Word', // MISSING
		emptyDic		: 'Dictionary name should not be empty.', // MISSING
		optionsTab		: 'Options', // MISSING
		languagesTab	: 'Languages', // MISSING
		dictionariesTab	: 'Dictionaries', // MISSING
		aboutTab		: 'About' // MISSING
	},

	about :
	{
		title		: 'About CKEditor', // MISSING
		dlgTitle	: 'About CKEditor', // MISSING
		moreInfo	: 'For licensing information please visit our web site:', // MISSING
		copy		: 'Copyright © $1. All rights reserved.' // MISSING
	},

	maximize : 'Maximize', // MISSING
	minimize : 'Minimize', // MISSING

	fakeobjects :
	{
		anchor	: 'Anchor', // MISSING
		flash	: 'Flash Animation', // MISSING
		div		: 'Page Break', // MISSING
		unknown	: 'Unknown Object' // MISSING
	},

	resize : 'Drag to resize', // MISSING

	colordialog :
	{
		title		: 'Select color', // MISSING
		highlight	: 'Highlight', // MISSING
		selected	: 'Selected', // MISSING
		clear		: 'Clear' // MISSING
	},

	toolbarCollapse	: 'Collapse Toolbar', // MISSING
	toolbarExpand	: 'Expand Toolbar' // MISSING
};


\NeedsTeXFormat{LaTeX2e}[1994/06/01]

\ProvidesClass{tufte-book}[2009/12/11 v3.5.0 Tufte-book class]

%%
% Declare we're tufte-book
\newcommand{\@tufte@class}{book}% the base LaTeX class (defaults to the article/handout style)
\newcommand{\@tufte@pkgname}{tufte-book}% the name of the package (defaults to tufte-handout)

%%
% Load the common style elements
\input{tufte-common.def}

%%
% Set up any book-specific stuff now

%%
% The front matter in Tufte's /Beautiful Evidence/ contains everything up
% to the opening page of Chapter 1.  The running heads, when they appear,
% contain only the (arabic) page number in the outside corner.
%\newif\if@mainmatter \@mainmattertrue
\renewcommand\frontmatter{%
  \cleardoublepage%
  \@mainmatterfalse%
  %\pagenumbering{arabic}%
  %\pagestyle{plain}%
  \fancyhf{}%
  \ifthenelse{\boolean{@tufte@twoside}}%
    {\fancyfoot[LE,RO]{\thepage}}%
    {\fancyfoot[RE,RO]{\thepage}}%\fancyfoot[R]{\thepage}
}

%%
% The main matter in Tufte's /Beautiful Evidence/ doesn't restart the page
% numbering---it continues where it left off in the front matter.
\renewcommand\mainmatter{%
  \cleardoublepage%
  \@mainmattertrue%
  \fancyhf{}%
  \ifthenelse{\boolean{@tufte@twoside}}%

}

%%
% The back matter contains appendices, indices, glossaries, endnotes,
% biliographies, list of contributors, illustration credits, etc.
\renewcommand\backmatter{%
  \if@openright%
    \cleardoublepage%
  \else%
    \clearpage%
  \fi%
  \@mainmatterfalse%
}

%%
% Only show the chapter titles in the table of contents
\setcounter{tocdepth}{0}

%%
% If there is a `tufte-book-local.sty' file, load it.

\IfFileExists{tufte-book-local.tex}
  {\input{tufte-book-local}
   \TufteInfoNL{Loading tufte-book-local.tex}}
  {}

%%
% End of file
\endinput


/*
===========================================================================

Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.

This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).

RTCW SP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RTCW SP Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

// Rafael particles
// cg_particles.c

#include "cg_local.h"

#define MUSTARD     1
#define BLOODRED    2
#define EMISIVEFADE 3
#define GREY75      4
#define ZOMBIE      5

typedef struct particle_s
{
	struct particle_s   *next;

	float time;
	float endtime;

	vec3_t org;
	vec3_t vel;
	vec3_t accel;
	int color;
	float colorvel;
	float alpha;
	float alphavel;
	int type;
	qhandle_t pshader;

	float height;
	float width;

	float endheight;
	float endwidth;

	float start;
	float end;

	float startfade;
	qboolean rotate;
	int snum;

	qboolean link;

	// Ridah
	int shaderAnim;
	int roll;

	int accumroll;

} cparticle_t;

typedef enum
{
	P_NONE,
	P_WEATHER,
	P_FLAT,
	P_SMOKE,
	P_ROTATE,
	P_WEATHER_TURBULENT,
	P_ANIM, // Ridah
	P_BAT,
	P_BLEED,
	P_FLAT_SCALEUP,
	P_FLAT_SCALEUP_FADE,
	P_WEATHER_FLURRY,
	P_SMOKE_IMPACT,
	P_BUBBLE,
	P_BUBBLE_TURBULENT,
	P_SPRITE
} particle_type_t;

#define MAX_SHADER_ANIMS        8
#define MAX_SHADER_ANIM_FRAMES  64
static char *shaderAnimNames[MAX_SHADER_ANIMS] = {
	"explode1",
	"blacksmokeanim",
	"twiltb2",
	"expblue",
	"blacksmokeanimb",   // uses 'explode1' sequence
	"blood",
	NULL
};
static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES];
static int shaderAnimCounts[MAX_SHADER_ANIMS] = {
	23,
	23, // (SA) removing warning messages from startup
	45,
	25,
	23,
	5,
};
static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = {
	1.405,
	1,
	1,
	1,
	1,
	1,
};
static int numShaderAnims;
// done.

#define     PARTICLE_GRAVITY    40
#define     MAX_PARTICLES   1024 * 8

cparticle_t *active_particles, *free_particles;
cparticle_t particles[MAX_PARTICLES];
int cl_numparticles = MAX_PARTICLES;

qboolean initparticles = qfalse;
vec3_t vforward, vright, vup;
vec3_t rforward, rright, rup;

float oldtime;

/*
==============
CG_ParticleLODCheck
==============
*/
qboolean CG_ParticleLODCheck( void ) {
	if ( cg_particleLOD.integer <= 1 ) {
		return qtrue;
	}

	if ( !( rand() % ( cg_particleLOD.integer ) ) ) { // let particle lod thin out particles
		return qtrue;
	}

	return qfalse;
}

/*
===============
CL_ClearParticles
===============
*/
void CG_ClearParticles( void ) {
	int i;

	memset( particles, 0, sizeof( particles ) );

	free_particles = &particles[0];
	active_particles = NULL;

	for ( i = 0 ; i < cl_numparticles ; i++ )
	{
		particles[i].next = &particles[i + 1];
		particles[i].type = 0;
	}
	particles[cl_numparticles - 1].next = NULL;

	oldtime = cg.time;

	// Ridah, init the shaderAnims
	for ( i = 0; shaderAnimNames[i]; i++ ) {
		int j;

		for ( j = 0; j < shaderAnimCounts[i]; j++ ) {
			shaderAnims[i][j] = trap_R_RegisterShader( va( "%s%i", shaderAnimNames[i], j + 1 ) );
		}
	}
	numShaderAnims = i;
	// done.

	initparticles = qtrue;
}

/*
=====================
CG_AddParticleToScene
=====================
*/
void CG_AddParticleToScene( cparticle_t *p, vec3_t org, float alpha ) {

	vec3_t point;
	polyVert_t verts[4];
	float width;
	float height;
	float time, time2;
	float ratio;
	float invratio;
	vec3_t color;
	polyVert_t TRIverts[3];
	vec3_t rright2, rup2;

	if ( p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY
		 || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) { // create a front facing polygon

		if ( p->type != P_WEATHER_FLURRY ) {
			if ( p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) {
				if ( org[2] > p->end ) {
					p->time = cg.time;
					VectorCopy( org, p->org ); // Ridah, fixes rare snow flakes that flicker on the ground

					p->org[2] = ( p->start + crandom() * 4 );

					if ( p->type == P_BUBBLE_TURBULENT ) {
						p->vel[0] = crandom() * 4;
						p->vel[1] = crandom() * 4;
					}

				}
			} else
			{
				if ( org[2] < p->end ) {
					p->time = cg.time;
					VectorCopy( org, p->org ); // Ridah, fixes rare snow flakes that flicker on the ground

					while ( p->org[2] < p->end )
					{
						p->org[2] += ( p->start - p->end );
					}

					if ( p->type == P_WEATHER_TURBULENT ) {
						p->vel[0] = crandom() * 16;
						p->vel[1] = crandom() * 16;
					}

				}
			}

			// Rafael snow pvs check
			if ( !p->link ) {
				return;
			}

			p->alpha = 1;
		}

		// Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp
		//----(SA)	made the dist a cvar

		// dot product removal  (gets you the dist^2, which you needed anyway, also dot lets you adjust lod when zooming)
		if ( 1 ) {
			vec3_t dir;
			float dot, distSqrd;

			VectorSubtract( cg.refdef.vieworg, org, dir );
			distSqrd = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];

			dot = DotProduct( dir, cg.refdef.viewaxis[0] );

			if ( distSqrd > ( cg_particleDist.value * cg_particleDist.value ) ) {
				return;
			}
		}

		// done.

		if ( p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) {
			VectorMA( org, -p->height, vup, point );
			VectorMA( point, -p->width, vright, point );
			VectorCopy( point, verts[0].xyz );
			verts[0].st[0] = 0;
			verts[0].st[1] = 0;
			verts[0].modulate[0] = 255;
			verts[0].modulate[1] = 255;
			verts[0].modulate[2] = 255;
			verts[0].modulate[3] = 255 * p->alpha;

			VectorMA( org, -p->height, vup, point );
			VectorMA( point, p->width, vright, point );
			VectorCopy( point, verts[1].xyz );
			verts[1].st[0] = 0;
			verts[1].st[1] = 1;
			verts[1].modulate[0] = 255;
			verts[1].modulate[1] = 255;
			verts[1].modulate[2] = 255;
			verts[1].modulate[3] = 255 * p->alpha;

			VectorMA( org, p->height, vup, point );
			VectorMA( point, p->width, vright, point );
			VectorCopy( point, verts[2].xyz );
			verts[2].st[0] = 1;
			verts[2].st[1] = 1;
			verts[2].modulate[0] = 255;
			verts[2].modulate[1] = 255;
			verts[2].modulate[2] = 255;
			verts[2].modulate[3] = 255 * p->alpha;

			VectorMA( org, p->height, vup, point );
			VectorMA( point, -p->width, vright, point );
			VectorCopy( point, verts[3].xyz );
			verts[3].st[0] = 1;
			verts[3].st[1] = 0;
			verts[3].modulate[0] = 255;
			verts[3].modulate[1] = 255;
			verts[3].modulate[2] = 255;
			verts[3].modulate[3] = 255 * p->alpha;
		} else
		{
			VectorMA( org, -p->height, vup, point );
			VectorMA( point, -p->width, vright, point );
			VectorCopy( point, TRIverts[0].xyz );
			TRIverts[0].st[0] = 1;
			TRIverts[0].st[1] = 0;
			TRIverts[0].modulate[0] = 255;
			TRIverts[0].modulate[1] = 255;
			TRIverts[0].modulate[2] = 255;
			TRIverts[0].modulate[3] = 255 * p->alpha;

			VectorMA( org, p->height, vup, point );
			VectorMA( point, -p->width, vright, point );
			VectorCopy( point, TRIverts[1].xyz );
			TRIverts[1].st[0] = 0;
			TRIverts[1].st[1] = 0;
			TRIverts[1].modulate[0] = 255;
			TRIverts[1].modulate[1] = 255;
			TRIverts[1].modulate[2] = 255;
			TRIverts[1].modulate[3] = 255 * p->alpha;

			VectorMA( org, p->height, vup, point );
			VectorMA( point, p->width, vright, point );
			VectorCopy( point, TRIverts[2].xyz );
			TRIverts[2].st[0] = 0;
			TRIverts[2].st[1] = 1;
			TRIverts[2].modulate[0] = 255;
			TRIverts[2].modulate[1] = 255;
			TRIverts[2].modulate[2] = 255;
			TRIverts[2].modulate[3] = 255 * p->alpha;
		}

	} else if ( p->type == P_SPRITE )     {
		vec3_t rr, ru;
		vec3_t rotate_ang;

		VectorSet( color, 1.0, 1.0, 1.0 );
		time = cg.time - p->time;
		time2 = p->endtime - p->time;
		ratio = time / time2;

		width = p->width + ( ratio * ( p->endwidth - p->width ) );
		height = p->height + ( ratio * ( p->endheight - p->height ) );

		if ( p->roll ) {
			vectoangles( cg.refdef.viewaxis[0], rotate_ang );
			rotate_ang[ROLL] += p->roll;
			AngleVectors( rotate_ang, NULL, rr, ru );
		}

		if ( p->roll ) {
			VectorMA( org, -height, ru, point );
			VectorMA( point, -width, rr, point );
		} else {
			VectorMA( org, -height, vup, point );
			VectorMA( point, -width, vright, point );
		}
		VectorCopy( point, verts[0].xyz );
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 255;
		verts[0].modulate[1] = 255;
		verts[0].modulate[2] = 255;
		verts[0].modulate[3] = 255;

		if ( p->roll ) {
			VectorMA( point, 2 * height, ru, point );
		} else {
			VectorMA( point, 2 * height, vup, point );
		}
		VectorCopy( point, verts[1].xyz );
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 255;
		verts[1].modulate[1] = 255;
		verts[1].modulate[2] = 255;
		verts[1].modulate[3] = 255;

		if ( p->roll ) {
			VectorMA( point, 2 * width, rr, point );
		} else {
			VectorMA( point, 2 * width, vright, point );
		}
		VectorCopy( point, verts[2].xyz );
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 255;
		verts[2].modulate[1] = 255;
		verts[2].modulate[2] = 255;
		verts[2].modulate[3] = 255;

		if ( p->roll ) {
			VectorMA( point, -2 * height, ru, point );
		} else {
			VectorMA( point, -2 * height, vup, point );
		}
		VectorCopy( point, verts[3].xyz );
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 255;
		verts[3].modulate[1] = 255;
		verts[3].modulate[2] = 255;
		verts[3].modulate[3] = 255;
	} else if ( p->type == P_SMOKE || p->type == P_SMOKE_IMPACT )     { // create a front rotating facing polygon

//		if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) {
//			return;
//		}

		// dot product removal  (gets you the dist^2, which you needed anyway, also dot lets you adjust lod when zooming)
		if ( 1 ) {
			vec3_t dir;
			float dot, distSqrd, fardist;

			VectorSubtract( org, cg.refdef.vieworg, dir );
			distSqrd = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];

			VectorNormalize( dir );
			dot = DotProduct( dir, cg.refdef.viewaxis[0] );

			if ( dot < 0 ) { // behind camera
				return;
			}

			fardist = ( cg_particleDist.value * cg_particleDist.value );
			// push distance out when zooming
			if ( cg.predictedPlayerState.eFlags & EF_ZOOMING ) {
				fardist *= 2;
			}

//			if(fabs(dot) < 0.8)
//				return;

			if ( distSqrd > fardist ) {
				return;
			}
		}

		if ( p->color == MUSTARD ) {
			VectorSet( color, 0.42, 0.33, 0.19 );
		} else if ( p->color == BLOODRED ) {
			VectorSet( color, 0.22, 0, 0 );
		} else if ( p->color == ZOMBIE ) {
			VectorSet( color, 0.4, 0.28, 0.23 );
		} else if ( p->color == GREY75 ) {
			float len;
			float greyit;
			float val;
			len = Distance( cg.snap->ps.origin, org );
			if ( !len ) {
				len = 1;
			}

			val = 4096 / len;
			greyit = 0.25 * val;
			if ( greyit > 0.5 ) {
				greyit = 0.5;
			}

			VectorSet( color, greyit, greyit, greyit );
		} else {
			VectorSet( color, 1.0, 1.0, 1.0 );
		}

		time = cg.time - p->time;
		time2 = p->endtime - p->time;
		ratio = time / time2;

		if ( cg.time > p->startfade ) {
			invratio = 1 - ( ( cg.time - p->startfade ) / ( p->endtime - p->startfade ) );

			if ( p->color == EMISIVEFADE ) {
				float fval;
				fval = ( invratio * invratio );
				if ( fval < 0 ) {
					fval = 0;
				}
				VectorSet( color, fval, fval, fval );
			}
			invratio *= p->alpha;
		} else {
			invratio = 1 * p->alpha;
		}

		if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) {
			invratio = 1;
		}

		if ( invratio > 1 ) {
			invratio = 1;
		}

		width = p->width + ( ratio * ( p->endwidth - p->width ) );
		height = p->height + ( ratio * ( p->endheight - p->height ) );

//		if (p->type != P_SMOKE_IMPACT)
		{
			vec3_t temp;

			vectoangles( rforward, temp );
			p->accumroll += p->roll;
			temp[ROLL] += p->accumroll * 0.1;
//			temp[ROLL] += p->roll * 0.1;
			AngleVectors( temp, NULL, rright2, rup2 );
		}
//		else
//		{
//			VectorCopy (rright, rright2);
//			VectorCopy (rup, rup2);
//		}

		if ( p->rotate ) {
			VectorMA( org, -height, rup2, point );
			VectorMA( point, -width, rright2, point );
		} else
		{
			VectorMA( org, -p->height, vup, point );
			VectorMA( point, -p->width, vright, point );
		}
		VectorCopy( point, verts[0].xyz );
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 255 * color[0];
		verts[0].modulate[1] = 255 * color[1];
		verts[0].modulate[2] = 255 * color[2];
		verts[0].modulate[3] = 255 * invratio;

		if ( p->rotate ) {
			VectorMA( org, -height, rup2, point );
			VectorMA( point, width, rright2, point );
		} else
		{
			VectorMA( org, -p->height, vup, point );
			VectorMA( point, p->width, vright, point );
		}
		VectorCopy( point, verts[1].xyz );
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 255 * color[0];
		verts[1].modulate[1] = 255 * color[1];
		verts[1].modulate[2] = 255 * color[2];
		verts[1].modulate[3] = 255 * invratio;

		if ( p->rotate ) {
			VectorMA( org, height, rup2, point );
			VectorMA( point, width, rright2, point );
		} else
		{
			VectorMA( org, p->height, vup, point );
			VectorMA( point, p->width, vright, point );
		}
		VectorCopy( point, verts[2].xyz );
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 255 * color[0];
		verts[2].modulate[1] = 255 * color[1];
		verts[2].modulate[2] = 255 * color[2];
		verts[2].modulate[3] = 255 * invratio;

		if ( p->rotate ) {
			VectorMA( org, height, rup2, point );
			VectorMA( point, -width, rright2, point );
		} else
		{
			VectorMA( org, p->height, vup, point );
			VectorMA( point, -p->width, vright, point );
		}
		VectorCopy( point, verts[3].xyz );
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 255 * color[0];
		verts[3].modulate[1] = 255 * color[1];
		verts[3].modulate[2] = 255 * color[2];
		verts[3].modulate[3] = 255  * invratio;

	} else if ( p->type == P_BAT )     {
		p->pshader = cgs.media.bats[( cg.time / 50 + (int)( p - particles ) ) % 10];

		VectorMA( org, -p->height, vup, point );
		VectorMA( point, -p->width, vright, point );
		VectorCopy( point, verts[0].xyz );
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 255;
		verts[0].modulate[1] = 255;
		verts[0].modulate[2] = 255;
		verts[0].modulate[3] = 255;

		VectorMA( org, -p->height, vup, point );
		VectorMA( point, p->width, vright, point );
		VectorCopy( point, verts[1].xyz );
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 255;
		verts[1].modulate[1] = 255;
		verts[1].modulate[2] = 255;
		verts[1].modulate[3] = 255;

		VectorMA( org, p->height, vup, point );
		VectorMA( point, p->width, vright, point );
		VectorCopy( point, verts[2].xyz );
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 255;
		verts[2].modulate[1] = 255;
		verts[2].modulate[2] = 255;
		verts[2].modulate[3] = 255;

		VectorMA( org, p->height, vup, point );
		VectorMA( point, -p->width, vright, point );
		VectorCopy( point, verts[3].xyz );
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 255;
		verts[3].modulate[1] = 255;
		verts[3].modulate[2] = 255;
		verts[3].modulate[3] = 255;

	} else if ( p->type == P_BLEED )     {
		vec3_t rr, ru;
		vec3_t rotate_ang;
		float alpha;

		alpha = p->alpha;

		if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) {
			alpha = 1;
		}

		if ( p->roll ) {
			vectoangles( cg.refdef.viewaxis[0], rotate_ang );
			rotate_ang[ROLL] += p->roll;
			AngleVectors( rotate_ang, NULL, rr, ru );
		} else
		{
			VectorCopy( vup, ru );
			VectorCopy( vright, rr );
		}

		VectorMA( org, -p->height, ru, point );
		VectorMA( point, -p->width, rr, point );
		VectorCopy( point, verts[0].xyz );
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 111;
		verts[0].modulate[1] = 19;
		verts[0].modulate[2] = 9;
		verts[0].modulate[3] = 255 * alpha;

		VectorMA( org, -p->height, ru, point );
		VectorMA( point, p->width, rr, point );
		VectorCopy( point, verts[1].xyz );
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 111;
		verts[1].modulate[1] = 19;
		verts[1].modulate[2] = 9;
		verts[1].modulate[3] = 255 * alpha;

		VectorMA( org, p->height, ru, point );
		VectorMA( point, p->width, rr, point );
		VectorCopy( point, verts[2].xyz );
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 111;
		verts[2].modulate[1] = 19;
		verts[2].modulate[2] = 9;
		verts[2].modulate[3] = 255 * alpha;

		VectorMA( org, p->height, ru, point );
		VectorMA( point, -p->width, rr, point );
		VectorCopy( point, verts[3].xyz );
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 111;
		verts[3].modulate[1] = 19;
		verts[3].modulate[2] = 9;
		verts[3].modulate[3] = 255 * alpha;

	} else if ( p->type == P_FLAT_SCALEUP )     {
		float width, height;
		float sinR, cosR;

		if ( p->color == BLOODRED ) {
			VectorSet( color, 1, 1, 1 );
		} else {
			VectorSet( color, 0.5, 0.5, 0.5 );
		}

		time = cg.time - p->time;
		time2 = p->endtime - p->time;
		ratio = time / time2;

		width = p->width + ( ratio * ( p->endwidth - p->width ) );
		height = p->height + ( ratio * ( p->endheight - p->height ) );

		if ( width > p->endwidth ) {
			width = p->endwidth;
		}

		if ( height > p->endheight ) {
			height = p->endheight;
		}

		sinR = height * sin( DEG2RAD( p->roll ) ) * sqrt( 2 );
		cosR = width * cos( DEG2RAD( p->roll ) ) * sqrt( 2 );

		VectorCopy( org, verts[0].xyz );
		verts[0].xyz[0] -= sinR;
		verts[0].xyz[1] -= cosR;
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 255 * color[0];
		verts[0].modulate[1] = 255 * color[1];
		verts[0].modulate[2] = 255 * color[2];
		verts[0].modulate[3] = 255;

		VectorCopy( org, verts[1].xyz );
		verts[1].xyz[0] -= cosR;
		verts[1].xyz[1] += sinR;
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 255 * color[0];
		verts[1].modulate[1] = 255 * color[1];
		verts[1].modulate[2] = 255 * color[2];
		verts[1].modulate[3] = 255;

		VectorCopy( org, verts[2].xyz );
		verts[2].xyz[0] += sinR;
		verts[2].xyz[1] += cosR;
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 255 * color[0];
		verts[2].modulate[1] = 255 * color[1];
		verts[2].modulate[2] = 255 * color[2];
		verts[2].modulate[3] = 255;

		VectorCopy( org, verts[3].xyz );
		verts[3].xyz[0] += cosR;
		verts[3].xyz[1] -= sinR;
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 255 * color[0];
		verts[3].modulate[1] = 255 * color[1];
		verts[3].modulate[2] = 255 * color[2];
		verts[3].modulate[3] = 255;
	} else if ( p->type == P_FLAT )     {

		VectorCopy( org, verts[0].xyz );
		verts[0].xyz[0] -= p->height;
		verts[0].xyz[1] -= p->width;
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 255;
		verts[0].modulate[1] = 255;
		verts[0].modulate[2] = 255;
		verts[0].modulate[3] = 255;

		VectorCopy( org, verts[1].xyz );
		verts[1].xyz[0] -= p->height;
		verts[1].xyz[1] += p->width;
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 255;
		verts[1].modulate[1] = 255;
		verts[1].modulate[2] = 255;
		verts[1].modulate[3] = 255;

		VectorCopy( org, verts[2].xyz );
		verts[2].xyz[0] += p->height;
		verts[2].xyz[1] += p->width;
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 255;
		verts[2].modulate[1] = 255;
		verts[2].modulate[2] = 255;
		verts[2].modulate[3] = 255;

		VectorCopy( org, verts[3].xyz );
		verts[3].xyz[0] += p->height;
		verts[3].xyz[1] -= p->width;
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 255;
		verts[3].modulate[1] = 255;
		verts[3].modulate[2] = 255;
		verts[3].modulate[3] = 255;

	}
	// Ridah
	else if ( p->type == P_ANIM ) {
		vec3_t rr, ru;
		vec3_t rotate_ang;
		int i, j;

		time = cg.time - p->time;
		time2 = p->endtime - p->time;
		ratio = time / time2;
		if ( ratio >= 1.0 ) {
			ratio = 0.9999;
		}

		width = p->width + ( ratio * ( p->endwidth - p->width ) );
		height = p->height + ( ratio * ( p->endheight - p->height ) );

		// if we are "inside" this sprite, don't draw
		if ( Distance( cg.snap->ps.origin, org ) < width / 1.5 ) {
			return;
		}

		i = p->shaderAnim;
		j = (int)floor( ratio * shaderAnimCounts[p->shaderAnim] );
		p->pshader = shaderAnims[i][j];

		if ( p->roll ) {
			vectoangles( cg.refdef.viewaxis[0], rotate_ang );
			rotate_ang[ROLL] += p->roll;
			AngleVectors( rotate_ang, NULL, rr, ru );
		}

		if ( p->roll ) {
			VectorMA( org, -height, ru, point );
			VectorMA( point, -width, rr, point );
		} else {
			VectorMA( org, -height, vup, point );
			VectorMA( point, -width, vright, point );
		}
		VectorCopy( point, verts[0].xyz );
		verts[0].st[0] = 0;
		verts[0].st[1] = 0;
		verts[0].modulate[0] = 255;
		verts[0].modulate[1] = 255;
		verts[0].modulate[2] = 255;
		verts[0].modulate[3] = 255;

		if ( p->roll ) {
			VectorMA( point, 2 * height, ru, point );
		} else {
			VectorMA( point, 2 * height, vup, point );
		}
		VectorCopy( point, verts[1].xyz );
		verts[1].st[0] = 0;
		verts[1].st[1] = 1;
		verts[1].modulate[0] = 255;
		verts[1].modulate[1] = 255;
		verts[1].modulate[2] = 255;
		verts[1].modulate[3] = 255;

		if ( p->roll ) {
			VectorMA( point, 2 * width, rr, point );
		} else {
			VectorMA( point, 2 * width, vright, point );
		}
		VectorCopy( point, verts[2].xyz );
		verts[2].st[0] = 1;
		verts[2].st[1] = 1;
		verts[2].modulate[0] = 255;
		verts[2].modulate[1] = 255;
		verts[2].modulate[2] = 255;
		verts[2].modulate[3] = 255;

		if ( p->roll ) {
			VectorMA( point, -2 * height, ru, point );
		} else {
			VectorMA( point, -2 * height, vup, point );
		}
		VectorCopy( point, verts[3].xyz );
		verts[3].st[0] = 1;
		verts[3].st[1] = 0;
		verts[3].modulate[0] = 255;
		verts[3].modulate[1] = 255;
		verts[3].modulate[2] = 255;
		verts[3].modulate[3] = 255;
	}
	// done.

	if ( !cg_wolfparticles.integer ) {
		return;
	}

	if ( !p->pshader ) {
// (SA) temp commented out for DM again.  FIXME: TODO: this needs to be addressed
//		CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type);
		return;
	}

	if ( p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY ) {
		trap_R_AddPolyToScene( p->pshader, 3, TRIverts );
	} else {
		trap_R_AddPolyToScene( p->pshader, 4, verts );
	}

}

// Ridah, made this static so it doesn't interfere with other files
static float roll = 0.0;

/*
===============
CG_AddParticles
===============
*/
void CG_AddParticles( void ) {
	cparticle_t     *p, *next;
	float alpha;
	float time, time2;
	vec3_t org;
	int color;
	cparticle_t     *active, *tail;
	int type;
	vec3_t rotate_ang;

	if ( !initparticles ) {
		CG_ClearParticles();
	}

	VectorCopy( cg.refdef.viewaxis[0], vforward );
	VectorCopy( cg.refdef.viewaxis[1], vright );
	VectorCopy( cg.refdef.viewaxis[2], vup );

	vectoangles( cg.refdef.viewaxis[0], rotate_ang );
	roll += ( ( cg.time - oldtime ) * 0.1 ) ;
	rotate_ang[ROLL] += ( roll * 0.9 );
	AngleVectors( rotate_ang, rforward, rright, rup );

	oldtime = cg.time;

	active = NULL;
	tail = NULL;

	for ( p = active_particles ; p ; p = next )
	{

		next = p->next;

		time = ( cg.time - p->time ) * 0.001;

		alpha = p->alpha + time * p->alphavel;
		if ( alpha <= 0 ) { // faded out
			p->next = free_particles;
			free_particles = p;
			p->type = 0;
			p->color = 0;
			p->alpha = 0;
			continue;
		}

		if ( p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT ) {
			if ( cg.time > p->endtime ) {
				p->next = free_particles;
				free_particles = p;
				p->type = 0;
				p->color = 0;
				p->alpha = 0;

				continue;
			}

		}

		if ( p->type == P_WEATHER_FLURRY ) {
			if ( cg.time > p->endtime ) {
				p->next = free_particles;
				free_particles = p;
				p->type = 0;
				p->color = 0;
				p->alpha = 0;

				continue;
			}
		}

		if ( p->type == P_FLAT_SCALEUP_FADE ) {
			if ( cg.time > p->endtime ) {
				p->next = free_particles;
				free_particles = p;
				p->type = 0;
				p->color = 0;
				p->alpha = 0;
				continue;
			}

		}

		if ( ( p->type == P_BAT || p->type == P_SPRITE ) && p->endtime < 0 ) {
			// temporary sprite
			CG_AddParticleToScene( p, p->org, alpha );
			p->next = free_particles;
			free_particles = p;
			p->type = 0;
			p->color = 0;
			p->alpha = 0;
			continue;
		}

		p->next = NULL;
		if ( !tail ) {
			active = tail = p;
		} else
		{
			tail->next = p;
			tail = p;
		}

		if ( alpha > 1.0 ) {
			alpha = 1;
		}

		color = p->color;

		time2 = time * time;

		org[0] = p->org[0] + p->vel[0] * time + p->accel[0] * time2;
		org[1] = p->org[1] + p->vel[1] * time + p->accel[1] * time2;
		org[2] = p->org[2] + p->vel[2] * time + p->accel[2] * time2;

		type = p->type;

		CG_AddParticleToScene( p, org, alpha );
	}

	active_particles = active;
}

/*
======================
CG_AddParticles
======================
*/
void CG_ParticleSnowFlurry( qhandle_t pshader, centity_t *cent ) {
	cparticle_t *p;
	qboolean turb = qtrue;

	if ( !pshader ) {
		CG_Printf( "CG_ParticleSnowFlurry pshader == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->color = 0;
	p->alpha = 0.90;
	p->alphavel = 0;

	p->start = cent->currentState.origin2[0];
	p->end = cent->currentState.origin2[1];

	p->endtime = cg.time + cent->currentState.time;
	p->startfade = cg.time + cent->currentState.time2;

	p->pshader = pshader;

	if ( rand() % 100 > 90 ) {
		p->height = 32;
		p->width = 32;
		p->alpha = 0.10;
	} else
	{
		p->height = 1;
		p->width = 1;
	}

	p->vel[2] = -20;

	p->type = P_WEATHER_FLURRY;

	if ( turb ) {
		p->vel[2] = -10;
	}

	VectorCopy( cent->currentState.origin, p->org );

	p->org[0] = p->org[0];
	p->org[1] = p->org[1];
	p->org[2] = p->org[2];

	p->vel[0] = p->vel[1] = 0;

	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->vel[0] += cent->currentState.angles[0] * 32 + ( crandom() * 16 );
	p->vel[1] += cent->currentState.angles[1] * 32 + ( crandom() * 16 );
	p->vel[2] += cent->currentState.angles[2];

	if ( turb ) {
		p->accel[0] = crandom() * 16;
		p->accel[1] = crandom() * 16;
	}

}

void CG_ParticleSnow( qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum ) {
	cparticle_t *p;

	if ( !pshader ) {
		CG_Printf( "CG_ParticleSnow pshader == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->color = 0;
	p->alpha = 0.40;
	p->alphavel = 0;
	p->start = origin[2];
	p->end = origin2[2];
	p->pshader = pshader;
	p->height = 1;
	p->width = 1;

	p->vel[2] = -50;

	if ( turb ) {
		p->type = P_WEATHER_TURBULENT;
		p->vel[2] = -50 * 1.3;
	} else
	{
		p->type = P_WEATHER;
	}

	VectorCopy( origin, p->org );

	p->org[0] = p->org[0] + ( crandom() * range );
	p->org[1] = p->org[1] + ( crandom() * range );
	p->org[2] = p->org[2] + ( crandom() * ( p->start - p->end ) );

	p->vel[0] = p->vel[1] = 0;

	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	if ( turb ) {
		p->vel[0] = crandom() * 16;
		p->vel[1] = crandom() * 16;
	}

	// Rafael snow pvs check
	p->snum = snum;
	p->link = qtrue;

}

void CG_ParticleBubble( qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum ) {
	cparticle_t *p;
	float randsize;

	if ( !pshader ) {
		CG_Printf( "CG_ParticleSnow pshader == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->color = 0;
	p->alpha = 0.40;
	p->alphavel = 0;
	p->start = origin[2];
	p->end = origin2[2];
	p->pshader = pshader;

	randsize = 1 + ( crandom() * 0.5 );

	p->height = randsize;
	p->width = randsize;

	p->vel[2] = 50 + ( crandom() * 10 );

	if ( turb ) {
		p->type = P_BUBBLE_TURBULENT;
		p->vel[2] = 50 * 1.3;
	} else
	{
		p->type = P_BUBBLE;
	}

	VectorCopy( origin, p->org );

	p->org[0] = p->org[0] + ( crandom() * range );
	p->org[1] = p->org[1] + ( crandom() * range );
	p->org[2] = p->org[2] + ( crandom() * ( p->start - p->end ) );

	p->vel[0] = p->vel[1] = 0;

	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	if ( turb ) {
		p->vel[0] = crandom() * 4;
		p->vel[1] = crandom() * 4;
	}

	// Rafael snow pvs check
	p->snum = snum;
	p->link = qtrue;

}

void CG_ParticleSmoke( qhandle_t pshader, centity_t *cent ) {

	// using cent->density = enttime
	//		 cent->frame = startfade
	cparticle_t *p;
	vec3_t dir;

	if ( !pshader ) {
		CG_Printf( "CG_ParticleSmoke == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	p->endtime = cg.time + cent->currentState.time;
	p->startfade = cg.time + cent->currentState.time2;

	p->color = 0;
	p->alpha = 1.0;
	p->alphavel = 0;
	p->start = cent->currentState.origin[2];
	p->end = cent->currentState.origin2[2];
	p->pshader = pshader;
	if ( cent->currentState.density == 1 ) {
		p->rotate = qfalse;
		p->height = 8;
		p->width = 8;
		p->endheight = 32;
		p->endwidth = 32;
	} else if ( cent->currentState.density == 2 )     {
		p->rotate = qtrue;
		p->height = 4;
		p->width = 4;
		p->endheight = 8;
		p->endwidth = 8;
	} else if ( cent->currentState.density == 3 )     {
		p->rotate = qfalse;
		{
			float scale;

			scale = 16 + ( crandom() * 8 );
			p->height = 24 + scale;
			p->width = 24 + scale;
			p->endheight = 64 + scale;
			p->endwidth = 64 + scale;
		}
	} else if ( cent->currentState.density == 4 )     { // white smoke
		p->rotate = qtrue;
		p->height = cent->currentState.angles2[0];
		p->width = cent->currentState.angles2[0];
		p->endheight = cent->currentState.angles2[1];
		p->endwidth = cent->currentState.angles2[1];
		p->color = GREY75;
	} else if ( cent->currentState.density == 5 )     { // mustard gas
		p->rotate = qtrue;
		p->height = cent->currentState.angles2[0];
		p->width = cent->currentState.angles2[0];
		p->endheight = cent->currentState.angles2[1];
		p->endwidth = cent->currentState.angles2[1];
		p->color = MUSTARD;
		p->alpha = 0.75;
	} else // black smoke
	{
		p->rotate = qtrue;
		p->height = cent->currentState.angles2[0];
		p->width = cent->currentState.angles2[0];
		p->endheight = cent->currentState.angles2[1];
		p->endwidth = cent->currentState.angles2[1];

		{
			int rval;
			rval = rand() % 6;
			if ( rval == 1 ) {
				p->pshader = cgs.media.smokePuffShaderb1;
			} else if ( rval == 2 ) {
				p->pshader = cgs.media.smokePuffShaderb2;
			} else if ( rval == 3 ) {
				p->pshader = cgs.media.smokePuffShaderb3;
			} else if ( rval == 4 ) {
				p->pshader = cgs.media.smokePuffShaderb4;
			} else {
				p->pshader = cgs.media.smokePuffShaderb5;
			}
		}
	}

	p->type = P_SMOKE;

	VectorCopy( cent->currentState.origin, p->org );

	p->vel[0] = p->vel[1] = 0;
	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	if ( cent->currentState.density == 1 ) {
		p->vel[2] = 5;
	} else if ( cent->currentState.density == 2 ) {
		p->vel[2] = 5;
	} else if ( cent->currentState.density == 3 ) { // cannon
		VectorCopy( cent->currentState.origin2, dir );
		p->vel[0] = dir[0] * 128 + ( crandom() * 64 );
		p->vel[1] = dir[1] * 128 + ( crandom() * 64 );
		p->vel[2] = 15 + ( crandom() * 16 );
	} else if ( cent->currentState.density == 5 )     { // gas or cover smoke
		VectorCopy( cent->currentState.origin2, dir );
		p->vel[0] = dir[0] * 32 + ( crandom() * 16 );
		p->vel[1] = dir[1] * 32 + ( crandom() * 16 );
		p->vel[2] = 4 + ( crandom() * 2 );
	} else // smoke
	{
		VectorCopy( cent->currentState.origin2, dir );
		p->vel[0] = dir[0] + ( crandom() * p->height );
		p->vel[1] = dir[1] + ( crandom() * p->height );
		p->vel[2] = cent->currentState.angles2[2];
	}

	if ( cent->currentState.frame == 1 ) { // reverse gravity
		p->vel[2] *= -1;
	}

//	p->roll = 8 + (crandom() * 4);
	p->roll = rand() % ( 2 * 8 );
	p->roll -= 8;
}

void CG_ParticleBulletDebris( vec3_t org, vec3_t vel, int duration ) {

	cparticle_t *p;

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	p->endtime = cg.time + duration;
	p->startfade = cg.time + duration / 2;

	p->color = EMISIVEFADE;
	p->alpha = 1.0;
	p->alphavel = 0;

	p->height = 0.5;
	p->width = 0.5;
	p->endheight = 0.5;
	p->endwidth = 0.5;

	p->pshader = cgs.media.tracerShader;

	p->type = P_SMOKE;

	VectorCopy( org, p->org );

	p->vel[0] = vel[0];
	p->vel[1] = vel[1];
	p->vel[2] = vel[2];
	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->accel[2] = -60;
	p->vel[2] += -20;

}

// DHM - Nerve :: bullets hitting dirt

void CG_ParticleDirtBulletDebris( vec3_t org, vec3_t vel, int duration ) {
	int r = rand() % 3;
	cparticle_t *p;

	if ( !free_particles ) {
		return;
	}
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	p->endtime = cg.time + duration;
	p->startfade = cg.time + duration / 2;

	p->color = EMISIVEFADE;
	p->alpha = 1.0;
	p->alphavel = 0;

	p->height = 1.2;
	p->width = 1.2;
	p->endheight = 4.5;
	p->endwidth = 4.5;

	if ( r == 0 ) {
		p->pshader = cgs.media.dirtParticle1Shader;
	} else if ( r == 1 ) {
		p->pshader = cgs.media.dirtParticle2Shader;
	} else {
		p->pshader = cgs.media.dirtParticle3Shader;
	}

	p->type = P_SMOKE;

	VectorCopy( org, p->org );

	p->vel[0] = vel[0];
	p->vel[1] = vel[1];
	p->vel[2] = vel[2];
	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->accel[2] = -330;
	p->vel[2] += -20;
}

// NERVE - SMF :: the core of the dirt explosion
void CG_ParticleDirtBulletDebris_Core( vec3_t org, vec3_t vel, int duration,
									   float width, float height, float alpha, char *shadername ) { // JPW NERVE
//	int r = rand(); // TTimo: unused
	cparticle_t *p;

	if ( !free_particles ) {
		return;
	}
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	p->endtime = cg.time + duration;
	p->startfade = cg.time + duration / 2;

	p->color = EMISIVEFADE;
	p->alpha = alpha;
	p->alphavel = 0;

	p->height = width; // JPW NERVE was 512/5.f;
	p->width = height; // JPW NERVE was 128/5.f;
	p->endheight = p->height;
	p->endwidth = p->width;

	p->rotate = 0;

	p->pshader = trap_R_RegisterShader( shadername ); // JPW NERVE was "dirt_splash"

	p->type = P_SMOKE;

	VectorCopy( org, p->org );
	VectorCopy( vel, p->vel );

//	p->vel[0] = vel[0];
//	p->vel[1] = vel[1];
//	p->vel[2] = vel[2];
	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->accel[2] = -330;
//	p->vel[2] += -20;
}

/*
======================
CG_ParticleExplosion
======================
*/

void CG_ParticleExplosion( char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd ) {
	cparticle_t *p;
	int anim;

	if ( animStr < (char *)10 ) {
		CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" );
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	// find the animation string
	for ( anim = 0; shaderAnimNames[anim]; anim++ ) {
		if ( !Q_strcasecmp( animStr, shaderAnimNames[anim] ) ) {
			break;
		}
	}
	if ( !shaderAnimNames[anim] ) {
		CG_Error( "CG_ParticleExplosion: unknown animation string: %s\n", animStr );
		return;
	}

	if ( !free_particles ) {
		return;
	}
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->alpha = 1.0;
	p->alphavel = 0;

	if ( duration < 0 ) {
		duration *= -1;
		p->roll = 0;
	} else {
		p->roll = crandom() * 179;
	}

	p->shaderAnim = anim;

	p->width = sizeStart;
	p->height = sizeStart * shaderAnimSTRatio[anim];  // for sprites that are stretch in either direction

	p->endheight = sizeEnd;
	p->endwidth = sizeEnd * shaderAnimSTRatio[anim];

	p->endtime = cg.time + duration;

	p->type = P_ANIM;

	VectorCopy( origin, p->org );
	VectorCopy( vel, p->vel );
	VectorClear( p->accel );

}

// Rafael Shrapnel
void CG_AddParticleShrapnel( localEntity_t *le ) {
	return;
}
// done.

int CG_NewParticleArea( int num ) {
	// const char *str;
	char *str;
	char *token;
	int type;
	vec3_t origin, origin2;
	int i;
	float range = 0;
	int turb;
	int numparticles;
	int snum;

	str = (char *) CG_ConfigString( num );
	if ( !str[0] ) {
		return ( 0 );
	}

	// returns type 128 64 or 32
	token = COM_Parse( &str );
	type = atoi( token );

	if ( type == 1 ) {
		range = 128;
	} else if ( type == 2 ) {
		range = 64;
	} else if ( type == 3 ) {
		range = 32;
	} else if ( type == 0 ) {
		range = 256;
	} else if ( type == 4 ) {
		range = 8;
	} else if ( type == 5 ) {
		range = 16;
	} else if ( type == 6 ) {
		range = 32;
	} else if ( type == 7 ) {
		range = 64;
	}

	for ( i = 0; i < 3; i++ )
	{
		token = COM_Parse( &str );
		origin[i] = atof( token );
	}

	for ( i = 0; i < 3; i++ )
	{
		token = COM_Parse( &str );
		origin2[i] = atof( token );
	}

	token = COM_Parse( &str );
	numparticles = atoi( token );

	token = COM_Parse( &str );
	turb = atoi( token );

	token = COM_Parse( &str );
	snum = atoi( token );

	for ( i = 0; i < numparticles; i++ )
	{
		if ( type >= 4 ) {
			CG_ParticleBubble( cgs.media.waterBubbleShader, origin, origin2, turb, range, snum );
		} else {
			CG_ParticleSnow( cgs.media.snowShader, origin, origin2, turb, range, snum );
		}
	}

	return ( 1 );
}

void    CG_SnowLink( centity_t *cent, qboolean particleOn ) {
	cparticle_t     *p, *next;
	int id;

	id = cent->currentState.frame;

	for ( p = active_particles ; p ; p = next )
	{
		next = p->next;

		if ( p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT ) {
			if ( p->snum == id ) {
				if ( particleOn ) {
					p->link = qtrue;
				} else {
					p->link = qfalse;
				}
			}
		}

	}
}

void CG_ParticleBat( centity_t *cent ) {
	cparticle_t *p;
	vec3_t origin;

	if ( !free_particles ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->color = 0;
	p->alpha = 1.0;
	p->alphavel = 0;
	p->height = 4;
	p->width = 4;

	VectorCopy( cent->lerpOrigin, origin );
	VectorCopy( origin, p->org );
	VectorClear( p->vel );

	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->snum = cent->currentState.frame;

	p->type = P_BAT;
	p->endtime = -1;    // last one frame only
}

void CG_ParticleBats( qhandle_t pshader, centity_t *cent ) {
	cparticle_t *p;
	vec3_t origin;

	if ( !free_particles ) {
		return;
	}
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->color = 0;
	p->alpha = 0.40;
	p->alphavel = 0;
	p->pshader = pshader;
	p->height = 4;
	p->width = 4;

	VectorCopy( cent->currentState.origin, origin );
	VectorCopy( origin, p->org );

	p->org[0] = p->org[0] + ( crandom() * 32 );
	p->org[1] = p->org[1] + ( crandom() * 32 );
	p->org[2] = p->org[2] + ( crandom() * 32 );

	p->vel[0] = cent->currentState.angles[0] * cent->currentState.time;
	p->vel[1] = cent->currentState.angles[1] * cent->currentState.time;
	p->vel[2] = cent->currentState.angles[2] * cent->currentState.time;

	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->snum = cent->currentState.frame;

	p->type = P_BAT;
}

void CG_BatsUpdatePosition( centity_t *cent ) {
	cparticle_t     *p, *next;
	int id;
	float time;

	id = cent->currentState.frame;

	for ( p = active_particles ; p ; p = next )
	{
		next = p->next;

		if ( p->type == P_BAT ) {
			if ( p->snum == id ) {
				time = ( cg.time - p->time ) * 0.001;

				p->org[0] = p->org[0] + p->vel[0] * time;
				p->org[1] = p->org[1] + p->vel[1] * time;
				p->org[2] = p->org[2] + p->vel[2] * time;

				p->time = cg.time;

				p->vel[0] = cent->currentState.angles[0] * cent->currentState.time;
				p->vel[1] = cent->currentState.angles[1] * cent->currentState.time;
				p->vel[2] = cent->currentState.angles[2] * cent->currentState.time;

			}
		}

	}
}

void CG_ParticleImpactSmokePuffExtended( qhandle_t pshader, vec3_t origin, vec3_t dir, int radius, int lifetime, int vel, int acc, int maxroll, float alpha ) {
	cparticle_t *p;

	if ( !pshader ) {
		CG_Printf( "CG_ParticleImpactSmokePuff pshader == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->alpha = alpha;
	p->alphavel = 0;

	// (SA) roll either direction
	p->roll = rand() % ( 2 * maxroll );
//	p->roll = crandom()*(float)(maxroll*2);
	p->roll -= maxroll;

	p->pshader = pshader;

	p->endtime = cg.time + lifetime;
	p->startfade = cg.time + 100;

	p->width = rand() % 4 + radius;   //----(SA)
	p->height = rand() % 4 + radius;  //----(SA)

	p->endheight = p->height * 2;
	p->endwidth = p->width * 2;

	p->type = P_SMOKE_IMPACT;

	VectorCopy( origin, p->org );
	VectorScale( dir, vel, p->vel );
	VectorScale( dir, acc, p->accel );
//	VectorSet(p->vel, 0, 0, vel);
//	VectorSet(p->accel, 0, 0, acc);

	p->rotate = qtrue;
}

void CG_ParticleImpactSmokePuff( qhandle_t pshader, vec3_t origin ) {
	CG_ParticleImpactSmokePuffExtended( pshader, origin, tv( 0,0,1 ), 8, 500, 20, 20, 30, 0.25f );
}

void CG_Particle_Bleed( qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration ) {
	cparticle_t *p;

	if ( !pshader ) {
		CG_Printf( "CG_Particle_Bleed pshader == ZERO!\n" );
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	if ( !free_particles ) {
		return;
	}
	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->alpha = 1.0;
	p->alphavel = 0;
	p->roll = 0;

	p->pshader = pshader;

	p->endtime = cg.time + duration;

	if ( fleshEntityNum ) {
		p->startfade = cg.time;
	} else {
		p->startfade = cg.time + 100;
	}

	p->width = 4;
	p->height = 4;

	p->endheight = 4 + rand() % 3;
	p->endwidth = p->endheight;

	p->type = P_SMOKE;

	VectorCopy( start, p->org );
	p->vel[0] = 0;
	p->vel[1] = 0;
	p->vel[2] = -20;
	VectorClear( p->accel );

	p->rotate = qfalse;

	p->roll = rand() % 179;

	if ( fleshEntityNum ) {
		p->color = MUSTARD;
	} else {
		p->color = BLOODRED;
	}
	p->alpha = 0.75;

}

//void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent)
void CG_Particle_OilParticle( qhandle_t pshader, vec3_t origin, vec3_t dir, int ptime, int snum ) {  // snum is parent ent number?
	cparticle_t *p;

	int time;
	int time2;
	float ratio;

//	float	duration = 1500;
	float duration = 2000;

	time = cg.time;
	time2 = cg.time + ptime;

	ratio = (float)1 - ( (float)time / (float)time2 );

	if ( !pshader ) {
		CG_Printf( "CG_Particle_OilParticle == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->alphavel = 0;
	p->roll = 0;

	p->pshader = pshader;

	p->endtime = cg.time + duration;

	p->startfade = p->endtime;

	p->width = 2;
	p->height = 2;

	p->endwidth = 1;
	p->endheight = 1;

	p->type = P_SMOKE;

	VectorCopy( origin, p->org );

	p->vel[0] = ( dir[0] * ( 16 * ratio ) );
	p->vel[1] = ( dir[1] * ( 16 * ratio ) );
	p->vel[2] = ( dir[2] * ( 16 * ratio ) );
//	p->vel[2] = (dir[2]);

	p->snum = snum;

	VectorClear( p->accel );

	p->accel[2] = -20;

	p->rotate = qfalse;

	p->roll = rand() % 179;

	p->alpha = 0.5;

	p->color = BLOODRED;

}

void CG_Particle_OilSlick( qhandle_t pshader, centity_t *cent ) {
	cparticle_t *p;

	if ( !pshader ) {
		CG_Printf( "CG_Particle_OilSlick == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	if ( cent->currentState.angles2[2] ) {
		p->endtime = cg.time + cent->currentState.angles2[2];
	} else {
		p->endtime = cg.time + 60000;
	}

	p->startfade = p->endtime;

	p->alpha = 1.0;
	p->alphavel = 0;
	p->roll = 0;

	p->pshader = pshader;

	if ( cent->currentState.angles2[0] || cent->currentState.angles2[1] ) {
		p->width = cent->currentState.angles2[0];
		p->height = cent->currentState.angles2[0];

		p->endheight = cent->currentState.angles2[1];
		p->endwidth = cent->currentState.angles2[1];
	} else
	{
		p->width = 8;
		p->height = 8;

		p->endheight = 16;
		p->endwidth = 16;
	}

	p->type = P_FLAT_SCALEUP;

	p->snum = cent->currentState.density;

	VectorCopy( cent->currentState.origin, p->org );

	p->org[2] += 0.55 + ( crandom() * 0.5 );

	p->vel[0] = 0;
	p->vel[1] = 0;
	p->vel[2] = 0;
	VectorClear( p->accel );

	p->rotate = qfalse;

	p->roll = rand() % 179;

	p->alpha = 0.75;

}

void CG_OilSlickRemove( centity_t *cent ) {
	cparticle_t     *p, *next;
	int id;

	id = cent->currentState.density;

	if ( !id ) {
		CG_Printf( "CG_OilSlickRevove NULL id\n" );
	}

	for ( p = active_particles ; p ; p = next )
	{
		next = p->next;

		if ( p->type == P_FLAT_SCALEUP ) {
			if ( p->snum == id ) {
				p->endtime = cg.time + 100;
				p->startfade = p->endtime;
				p->type = P_FLAT_SCALEUP_FADE;

			}
		}

	}
}

qboolean ValidBloodPool( vec3_t start ) {
#define EXTRUDE_DIST    0.5

	vec3_t angles;
	vec3_t right, up;
	vec3_t this_pos, x_pos, center_pos, end_pos;
	float x, y;
	float fwidth, fheight;
	trace_t trace;
	vec3_t normal;

	fwidth = 16;
	fheight = 16;

	VectorSet( normal, 0, 0, 1 );

	vectoangles( normal, angles );
	AngleVectors( angles, NULL, right, up );

	VectorMA( start, EXTRUDE_DIST, normal, center_pos );

	for ( x = -fwidth / 2; x < fwidth; x += fwidth )
	{
		VectorMA( center_pos, x, right, x_pos );

		for ( y = -fheight / 2; y < fheight; y += fheight )
		{
			VectorMA( x_pos, y, up, this_pos );
			VectorMA( this_pos, -EXTRUDE_DIST * 2, normal, end_pos );

			CG_Trace( &trace, this_pos, NULL, NULL, end_pos, -1, CONTENTS_SOLID );

			if ( trace.entityNum < ( MAX_ENTITIES - 1 ) ) { // may only land on world
				return qfalse;
			}

			if ( !( !trace.startsolid && trace.fraction < 1 ) ) {
				return qfalse;
			}

		}
	}

	return qtrue;
}

void CG_BloodPool( localEntity_t *le, qhandle_t pshader, trace_t *tr ) {
	cparticle_t *p;
	qboolean legit;
	vec3_t start;
	float rndSize;

	if ( !pshader ) {
		CG_Printf( "CG_BloodPool pshader == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	VectorCopy( tr->endpos, start );
	legit = ValidBloodPool( start );

	if ( !legit ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	p->endtime = cg.time + 3000;
	p->startfade = p->endtime;

	p->alpha = 1.0;
	p->alphavel = 0;
	p->roll = 0;

	p->pshader = pshader;

	rndSize = 0.4 + random() * 0.6;

	p->width = 8 * rndSize;
	p->height = 8 * rndSize;

	p->endheight = 16 * rndSize;
	p->endwidth = 16 * rndSize;

	p->type = P_FLAT_SCALEUP;

	VectorCopy( start, p->org );

	p->vel[0] = 0;
	p->vel[1] = 0;
	p->vel[2] = 0;
	VectorClear( p->accel );

	p->rotate = qfalse;

	p->roll = rand() % 179;

	p->alpha = 0.75;

	p->color = BLOODRED;
}

#define NORMALSIZE  16
#define LARGESIZE   32

void CG_ParticleBloodCloud( centity_t *cent, vec3_t origin, vec3_t dir ) {
	float length;
	float dist;
	float crittersize;
	vec3_t angles, forward;
	vec3_t point;
	cparticle_t *p;
	int i;

	dist = 0;

	length = VectorLength( dir );
	vectoangles( dir, angles );
	AngleVectors( angles, forward, NULL, NULL );

	if ( cent->currentState.density == 0 ) { // normal ai size
		crittersize = NORMALSIZE;
	} else {
		crittersize = LARGESIZE;
	}

	if ( length ) {
		dist = length / crittersize;
	}

	if ( dist < 1 ) {
		dist = 1;
	}

	VectorCopy( origin, point );

	for ( i = 0; i < dist; i++ )
	{
		VectorMA( point, crittersize, forward, point );

		if ( !free_particles ) {
			return;
		}

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cg.time;
		p->alpha = 1.0;
		p->alphavel = 0;
		p->roll = 0;

		p->pshader = cgs.media.smokePuffShader;

		p->endtime = cg.time + 450 + ( crandom() * 100 );

		if ( cent->currentState.aiChar == AICHAR_HELGA || cent->currentState.aiChar == AICHAR_HEINRICH ) {
			// stick around longer
			p->endtime += 3000;
		}

		p->startfade = cg.time;

		if ( cent->currentState.density == 0 ) { // normal ai size
			p->width = NORMALSIZE;
			p->height = NORMALSIZE;

			p->endheight = NORMALSIZE;
			p->endwidth = NORMALSIZE;
		} else // large frame
		{
			p->width = LARGESIZE;
			p->height = LARGESIZE;

			p->endheight = LARGESIZE;
			p->endwidth = LARGESIZE;
		}

		p->type = P_SMOKE;

		VectorCopy( origin, p->org );

		p->vel[0] = 0;
		p->vel[1] = 0;
		p->vel[2] = -1;

		VectorClear( p->accel );

		p->rotate = qfalse;

		p->roll = rand() % 179;

		if ( cent->currentState.aiChar == AICHAR_ZOMBIE ) {
			p->color = MUSTARD;
		} else {
			p->color = BLOODRED;
		}

		p->alpha = 0.75;

	}

}

void CG_ParticleBloodCloudZombie( centity_t *cent, vec3_t origin, vec3_t dir ) {
	float length;
	float dist;
	float crittersize;
	vec3_t angles, forward;
	vec3_t point;
	cparticle_t *p;
	int i;

	dist = 0;

	length = VectorLength( dir );
	vectoangles( dir, angles );
	AngleVectors( angles, forward, NULL, NULL );

	if ( cent->currentState.density == 0 ) { // normal ai size
		crittersize = NORMALSIZE / 4;
	} else {
		crittersize = LARGESIZE / 3;
	}

	if ( length ) {
		dist = length / crittersize;
	}

	if ( dist < 1 ) {
		dist = 1;
	}

	VectorCopy( origin, point );

	for ( i = 0; i < dist; i++ )
	{
		VectorMA( point, crittersize, forward, point );

		if ( !free_particles ) {
			return;
		}

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cg.time;
		p->alpha = 0.2;
		p->alphavel = 0;
		p->roll = 0;

		p->pshader = cgs.media.bloodCloudShader;

		// RF, stay around for long enough to expand and dissipate naturally
		if ( length ) {
			p->endtime = cg.time + 3500 + ( crandom() * 2000 );
		} else {
			p->endtime = cg.time + 750 + ( crandom() * 500 );
		}

		p->startfade = cg.time;

		if ( cent->currentState.density == 0 ) { // normal ai size
			p->width = NORMALSIZE;
			p->height = NORMALSIZE;

			// RF, expand while falling
			p->endheight = NORMALSIZE * 4.0;
			p->endwidth = NORMALSIZE * 4.0;
		} else // large frame
		{
			p->width = LARGESIZE;
			p->height = LARGESIZE;

			// RF, expand while falling
			p->endheight = LARGESIZE * 3.0;
			p->endwidth = LARGESIZE * 3.0;
		}

		if ( !length ) {
			p->width *= 0.2;
			p->height *= 0.2;

			p->endheight = NORMALSIZE;
			p->endwidth = NORMALSIZE;
		}

		p->type = P_SMOKE;

		VectorCopy( origin, p->org );

		p->vel[0] = crandom() * 6;
		p->vel[1] = crandom() * 6;
		p->vel[2] = random() * 6;

		// RF, add some gravity/randomness
		p->accel[0] = crandom() * 3;
		p->accel[1] = crandom() * 3;
		p->accel[2] = -PARTICLE_GRAVITY * 0.2;

		VectorClear( p->accel );

		p->rotate = qfalse;

		p->roll = rand() % 179;

		p->color = ZOMBIE;

	}

}

void CG_ParticleSparks( vec3_t org, vec3_t vel, int duration, float x, float y, float speed ) {
	cparticle_t *p;

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;

	p->endtime = cg.time + duration;
	p->startfade = cg.time + duration / 2;

	p->color = EMISIVEFADE;
	p->alpha = 0.4;
	p->alphavel = 0;

	p->height = 0.5;
	p->width = 0.5;
	p->endheight = 0.5;
	p->endwidth = 0.5;

	p->pshader = cgs.media.tracerShader;

	p->type = P_SMOKE;

	VectorCopy( org, p->org );

	p->org[0] += ( crandom() * x );
	p->org[1] += ( crandom() * y );

	p->vel[0] = vel[0];
	p->vel[1] = vel[1];
	p->vel[2] = vel[2];

	p->accel[0] = p->accel[1] = p->accel[2] = 0;

	p->vel[0] += ( crandom() * 4 );
	p->vel[1] += ( crandom() * 4 );
	p->vel[2] += ( 20 + ( crandom() * 10 ) ) * speed;

	p->accel[0] = crandom() * 4;
	p->accel[1] = crandom() * 4;

}

void CG_ParticleDust( centity_t *cent, vec3_t origin, vec3_t dir ) {
	float length;
	float dist;
	float crittersize;
	vec3_t angles, forward;
	vec3_t point;
	cparticle_t *p;
	int i;

	dist = 0;

	VectorNegate( dir, dir );
	length = VectorLength( dir );
	vectoangles( dir, angles );
	AngleVectors( angles, forward, NULL, NULL );

	if ( cent->currentState.density == 0 ) { // normal ai size
		crittersize = NORMALSIZE;
	} else {
		crittersize = LARGESIZE;
	}

	if ( length ) {
		dist = length / crittersize;
	}

	if ( dist < 1 ) {
		dist = 1;
	}

	VectorCopy( origin, point );

	for ( i = 0; i < dist; i++ )
	{
		VectorMA( point, crittersize, forward, point );

		if ( !free_particles ) {
			return;
		}

		p = free_particles;
		free_particles = p->next;
		p->next = active_particles;
		active_particles = p;

		p->time = cg.time;
		p->alpha = 5.0;
		p->alphavel = 0;
		p->roll = 0;

		p->pshader = cgs.media.bloodCloudShader;

		// RF, stay around for long enough to expand and dissipate naturally
		if ( length ) {
			p->endtime = cg.time + 4500 + ( crandom() * 3500 );
		} else {
			p->endtime = cg.time + 750 + ( crandom() * 500 );
		}

		p->startfade = cg.time;

		if ( cent->currentState.density == 0 ) { // normal ai size
			p->width = NORMALSIZE;
			p->height = NORMALSIZE;

			// RF, expand while falling
			p->endheight = NORMALSIZE * 4.0;
			p->endwidth = NORMALSIZE * 4.0;
		} else // large frame
		{
			p->width = LARGESIZE;
			p->height = LARGESIZE;

			// RF, expand while falling
			p->endheight = LARGESIZE * 3.0;
			p->endwidth = LARGESIZE * 3.0;
		}

		if ( !length ) {
			p->width *= 0.2;
			p->height *= 0.2;

			p->endheight = NORMALSIZE;
			p->endwidth = NORMALSIZE;
		}

		p->type = P_SMOKE;

		VectorCopy( point, p->org );

		p->vel[0] = crandom() * 6;
		p->vel[1] = crandom() * 6;
		p->vel[2] = random() * 20;

		// RF, add some gravity/randomness
		p->accel[0] = crandom() * 3;
		p->accel[1] = crandom() * 3;
		p->accel[2] = -PARTICLE_GRAVITY * 0.4;

		VectorClear( p->accel );

		p->rotate = qfalse;

		p->roll = rand() % 179;

		if ( cent->currentState.density ) {
			p->color = GREY75;
		} else {
			p->color = MUSTARD;
		}

		p->alpha = 0.75;

	}

}

void CG_ParticleMisc( qhandle_t pshader, vec3_t origin, int size, int duration, float alpha ) {
	cparticle_t *p;

	if ( !pshader ) {
		CG_Printf( "CG_ParticleImpactSmokePuff pshader == ZERO!\n" );
	}

	if ( !free_particles ) {
		return;
	}

	if ( !CG_ParticleLODCheck() ) {
		return;
	}

	p = free_particles;
	free_particles = p->next;
	p->next = active_particles;
	active_particles = p;
	p->time = cg.time;
	p->alpha = 1.0;
	p->alphavel = 0;
	p->roll = rand() % 179;

	p->pshader = pshader;

	if ( duration > 0 ) {
		p->endtime = cg.time + duration;
	} else {
		p->endtime = duration;
	}

	p->startfade = cg.time;

	p->width = size;
	p->height = size;

	p->endheight = size;
	p->endwidth = size;

	p->type = P_SPRITE;

	VectorCopy( origin, p->org );

	p->rotate = qfalse;
}


# Квартирный вопрос

Квартиры в Германии ищутся только лично, с визитом и осмотрами. Удаленно найти хороший вариант на долгий срок невозможно. Поэтому обычно последовательность выглядит так:
- Вы еще не приехали, но ищете место размещения на первое время. Вариантов не много - **Airbnb**, [**Mr. Lodge**](https://mrlodge.com) или какое-либо агентство (см. список ниже). Можете попробовать писать хозяевам по объявлениям, но шанс получить контракт исчезающе мал. **Внимание**: заранее узнавайте, чтобы в этом жилье была возможность зарегистрироваться (Anmeldung).  Иначе могут быть сложности с получением страховки, SCHUFA, открытием банковского счета.
- Вы уже приехали, вышли на работу, но находитесь на испытательном сроке (или на временном контракте, что еще хуже), не получали еще зарплат, возможно, не говорите по-немецки. На этом этапе ваша задача - получить хоть какой-нибудь контракт аренды на более-менее длинный срок (минимум полгода-год). В силу входных данных "только понаехавшего", это будет достаточно трудно, поскольку конкуренция среди арендаторов высока, на квартиру могут претендовать десятки, а то и сотни людей (в Берлине попроще, в Мюнхене посложнее). Но практика показывает, что за 3-4 недели обычно варианты находятся. Готовьтесь в первый месяц по нескольку раз в день проверять новые объявления, распечатывать пачками документы и тратить вечера на многочисленные осмотры квартир. Не отчаивайтесь, рано или поздно и на вашей улице будет праздник.
- Вы уже "понаехавший со стажем", стабильная зарплата последние месяцы, есть осознание того, что вокруг происходит. Можно искать "квартиру мечты", а не "что дают". Процесс ничем не отличается от предыдущего этапа за исключением, что теперь вы выглядите намного надежнее для арендодателей, поэтому предложений должно быть заметно больше и времени на поиск уйдет меньше.

## Пошаговая инструкция
В данном разделе будет сделан акцент на второй пункт поисков, поскольку он интереснее всех. Итак, как искать квартиры?

1. Выбираете какой-нибудь сайт из списка ниже, регистрируетесь там, заполняете по возможности профиль. Для начала рекомендуется https://www.immobilienscout24.de/ aka "скаут", как самый крупный.
2. Определяетесь с районом(-ами) поиска. Чем ближе к центру, тем дороже и сложнее снять. Самые бюджетные варианты находятся за чертой города. Поэтому полезно изучить транспортную схему выбранного района, какие виды транспорта там ходят - S-Bahn, U-Bahn, Bus, Tram - и с какой периодичностью. Проложите маршрут в Google Maps от дома до работы и оцените время в пути, количество пересадок, удаленность остановок от дома/работы и т.д.
3. На выбранных сайтах просматриваете появление новых квартир в районе поиска. В случае Скаута, лучше использовать мобильное приложение – оно на английском языке и имеет широкий функционал. Рекомендуется создать "Saved search", и тогда вам будут приходить push уведомления о новых объявлениях в момент их публикации. Чем раньше вы откликнетесь на объявление, тем больше вероятности, что вас позовут на просмотр. Также можно настроить уведомления на почту, ФБ Мессенджер или оповещения в браузере. Не забывайте, что спрос очень большой и скорость реакции имеет значение.
4. Готовите "вступительную речь" - сообщение, которое будете отправлять хозяевам при отклике на объявление. Рекомендуется писать или на немецком, или на немецком и английском. Предложите коллегам или знакомым оценить ваше творчество, если не уверены в знании языка.
5. **Самое важное!** Готовите комплект документов. Это именно то, что будет вас выделять среди множества других претендентов на квартиру, вполне возможно с лучшими входными данными. Когда вы после осмотра отдаете владельцу\агенту заранее подготовленную папку со всеми вашими документами, а не заполняете анкету на коленке в темноте не пишущей ручкой - это очень хороший плюс в вашу пользу. А поскольку других плюсов на данный момент у вас, скорее всего, нет, надо пользоваться возможностью по-максимуму. Также рекомендуется выложить электронные копии документов, например, на Google Drive или Dropbox, и посылать ссылку на них сразу же при отклике на объявление.
6. Собственно, с вашей стороны все. Теперь осталось постоянно проверять сервис на наличие новых объявлений и откликаться на все, что выглядит сколь угодно прилично и вписывается в ваш бюджет.
7. Если вас приглашают на осмотр - немедленно соглашаетесь и идете. Если в квартире все нравится, то отдаете хозяину или агенту ваш комплект документов и надеетесь на лучшее.
8. В один прекрасный день вам позвонят и скажут "Мы готовы сдать вам квартиру". Радуетесь, но не сильно, поскольку контракт еще не подписан. Договариваетесь с хозяином о контракте, читаете сами или даете почитать немецкоговорящим коллегам, подписываете - и на этом ваши мучения заканчиваются. Можете отключать уведомления, и, наконец-то, с утра вашим первым делом будет не просмотр новых объявлений и отклик, а душ и завтрак :). Ждете даты въезда.
9. При приемке квартиры внимательно смотрите на состояние, проверяете сантехнику, выключатели, электроприборы, осматриваете углы на предмет плесени. Фиксируете все неисправности/недочеты/замечания, иначе их спишут на вас по окончании вашего контракта. Не забывайте про условия возврата квартиры (должно быть описано в контракте): зачастую квартира вам сдается без мебели и свежеотремонтированная, вам, скорее всего, при выезде также придется побелить все стены и привести пол в исходное состояние (лакировать паркет\переложить ламинат).
10. Получаете ключи от квартиры, гаража, подвала (опционально) и начинаете наслаждаться жизнью.

## Словарик квартирного вопроса:
- Wohnung - собственно, "жилье".
- Zimmer - комната, встречается обычно в контексте "2 Zi Wohnung".
- Kalt - "холодная", "базовая" стоимость аренды без учета коммунальных и других услуг.
- Nebenkosten - дополнительные платежи, обычно сюда входит вода, отопление и прочее. **Внимание** - [электричество](./Электричество.md) почти никогда в Nebenkosten не входит, за него надо будет платить отдельно, как и за интернет.
- Warm - "теплая" цена, обычно Kalt + Nebenkosten, возможно плюс доплаты, например, за гараж. В общем, это та сумма, которую вы будете обязаны ежемесячно платить хозяину.
- Mieter - это вы, то бишь тот, кто снимает жилье, арендатор.
- Vermieter - это landlord, хозяин, кто сдает жилье, арендодатель.
- Besichtigungs Termin - личная встреча для осмотра квартиры.
- Mietvertrag - контракт на аренду жилья.
- WG (Wohngemeinschaft) - жильё для совместного проживания. При поиске комнаты ориентируемся именно на эту аббревиатуру.

## Документы
Как описано выше, рекомендуется заранее подготовить папку со всеми документами. А именно:
- Анкета, т.н. **Mieterselbstauskunft**. В интернете есть множество вариантов, один из самых простых и доступных вот [этот](http://www.mietrecht-hilfe.de/media/downloads/Selbstauskunft-Mieter-Vorlage.pdf). Анкета на двух человек, если вы собираетесь снимать один, можно во второй колонке ставить прочерки. Заполняете все правдиво, включая вашу зарплату.
- Последние три расчетных листка (**payslips**), если есть, или на худой конец контракт с указанием зарплаты. Конечно, не надо делать копию многостраничного контракта, но будьте готовы его показать по просьбе хозяина.
- **SCHUFA**. Что-то вроде вашей кредитной истории, а также некая оценка вас как плательщика. Даже если вы только приехали, рекомендуется ее получить. Зачастую арендодателям достаточно красивого листа "Zertifikat", на котором написано, что в вашей истории найдены только позитивные записи, но особо въедливые хозяева также могут просить лист, на котором видна ваша оценка (**SCHUFA-Basisscore**). См. детали ниже.
- Рекомендательное письмо с работы. Попросите компанию или начальника написать вам что-нибудь официальное на бланке с логотипом и печатью компании. Базовый минимум "подтверждаем, что такой-то такой-то работает в нашей компании с такого-то числа на бессрочном контракте".
- Рекомендательное письмо от текущего (предыдущего) хозяина. Скорее всего, даже на временной квартире у вас будет собственник квартиры или агентство - попросите их написать вам письмо о том, какой вы молодец, вовремя платите аренду и не устраиваете костров в комнате.
  Документ называется Mietschuldenfreiheitsbestätigung или [Mietzahlungsbestätigung](https://www.immobilienscout24.de/ratgeber/wohnungsbewerbung/mietzahlungsbestaetigung.html).
  Как [запросить](https://www.reddit.com/r/germany/comments/4qwt9f/q_how_to_request/).
- Копия паспорта. Есть мнение, что лучше замазать вашу подпись на копии.

Первые три пункта относятся к категории must have (Schufa, теоретически, можно показать потом, но лучше все-таки иметь все документы на руках). Остальные опционально, но очень рекомендуется.

## SCHUFA
SCHUFA - это крупная немецкая организация, которая, в числе прочего, занимается кредитными историями и оценкой "надежности" резидентов Германии. Если вы все платите вовремя, не получаете штрафов и имеете зарплату, то обычно никаких проблем возникнуть не должно. Но просрочки платежей, частые смены банка, злостные нарушения законов (повторные проезды без билета, например), большое количество кредитов - все это может негативно отразиться на вашей оценке, вследствие чего вам будет сложнее найти квартиру (какой хозяин захочет сдавать злостному неплательщику), банки откажут в кредите или выдадут под грабительский процент, и прочее.

Обычно под SCHUFA подразумевается именно выписка из "кредитной истории", включающая в себя сертификат о вашей надежности и лист с точной оценкой SCHUFA-Basisscore (в процентах от 0 до 100). Чем выше оценка, тем лучше. 95% и выше считается хорошей оценкой, 98% и выше - идеальной. Бумажная выписка актуальна три-четыре месяца, этого должно хватить на поиск квартиры даже в самом тяжелом случае.

Как получить:
  - Если нужно срочно, спрашивайте в банках Postbank или Volksbank ([см. карту отделений](https://www.meineschufa.de/index.php?site=14_3)). Надо иметь с собой регистрацию (адрес в Германии), паспорт и **немецкий счет в банке**. Делают за 10 минут. Стоит 29.95 евро. Деньги со счета будут списаны в течение 1-2 недель.
  - На основании § 34 Bundesdatenschutzgesetz SCHUFA можно получить бесплатно один раз в год, заполнив бумажную форму и отправив почтой. Следуйте этой довольно понятной [инструкции](http://www.ratgeber-geld.de/schufa/auskunft.html). Бланк можно найти [здесь](https://www.meineschufa.de/index.php?site=11_3_1).
  - Можно получить SCHUFA и через "скаут" - [ссылка на услугу](https://bonitaetscheck.immobilienscout24.de/). Стоит 29.95 евро. Присылают либо на почтовый ящик, либо дают скачать электронный вариант. Бонусом идет месячный пробный период премиум аккаунта (по состоянию на начало 2018г). Если премиум аккаунт не нужен (а он скорее всего не нужен), то лучше сразу его отключить, так как иначе он автоматически продлится и скаут втихую начнет каждый месяц списывать абонентскую плату.

### Полезные ссылки по поиску
Сайты для поиска:
* https://www.immobilienscout24.de/ - крупнейший сайт для поиска квартир в Германии,
* http://www.immonet.de/,
* https://www.immowelt.de/,
* http://www.wg-gesucht.de/,
* http://kleinanzeigen.ebay.de,
* https://www.akelius.de/ - это агентство, которое владеет многоквартирными домами под сдачу, вам помогает их агент, но платить ему не надо. Очень хорошие и говорят по-английски.
* ![](files/be.png)
  * https://www.coming-home.com/en/ - агентство, цены выше среднего, но квартиры все меблированные,
  * https://www.city-wohnen.de/eng/berlin/ - агентство, цены немного выше среднего, как правило в цену уже все включено (газ, вода, свет), квартиры все меблированные, быстро отвечают и хорошо подбирают вариант если точно опишете свои пожелания и район, общение на английском.
  * Группы в фейсбуке, объявлений много, но есть и мошенники: [1](https://www.facebook.com/groups/183048595060764), [2](https://www.facebook.com/groups/681027242074146/), [3](https://www.facebook.com/groups/berlinrooms), [4](https://www.facebook.com/groups/easy.wg), [5](https://www.facebook.com/groups/890919300923458),

Если квартира нужна срочно и меблированная:
* https://www.nestpick.com
* http://www.homecompany.de/en/index
* https://wunderflats.com - сервис для поиска квартир на первое время специально для переезжающих в Берлин, Мюнхен или Гамбург. Стоимость выше среднего, но в цену уже включены свет, газ, вода и интернет. Также предоставляется справка для банка (Wohungsgeberbescheinigung)
* ![](files/be.png)
  * https://www.berlinovo.de/de
  * https://www.central-home.de/en/home/
  * https://www.city-wohnen.de/eng/berlin/
* ![](files/mu.png)
  * https://www.mrlodge.ru/
* Frankfurt (M)
  * https://www.city-residence.de/ Квартиры на короткий срок (от 3 мес), можно снять удалённо, есть мебель. Владельцы говорят на английском. Можно прописаться. Цены, естественно, выше.

Системы автоматизации поиска и отслеживание обновлений:
* https://t.me/BerlinFlatsBot - telegram bot для Берлина, оповещает о появлении новых объявлений на популярных ресурсах;

## Агентство или собственники?
Квартиры можно снимать помимо “частников” также от фирм, например, так называемые Wohnungsgenossenschaften.
  - Преимущества в том, что, снимая у фирмы, нет недостатков “частников”, например, собственная необходимость, неправильное поведение арендодателя (разного рода странные требования и запреты), юридические аспекты (например, реже мухлюют с Nebenkostenabrechnung).
  - Wohnungsgenossenschaften обычно сдают жилье без маклера, т.е. маклерские можно сэкономить.
  - Недостатки: время ожидания квартиры в зависимости от фирмы и района может быть достаточно долгим Wohnungsgenossenschaften в Берлине [здесь](http://www.berlin.de/special/immobilien-und-wohnen/adressen/wohnungsbaugenossenschaft/)

## Черный список
Сдали очень проблемную недешевую квартиру, взяли комиссию, дело дошло до адвоката, по рассказам жильцов этого дома с ними судились пару раз, о проблемах явно знали и не предупредили:
 - Roseneck management
 - Pierre Kübler и его агентство Selectra
 - Дом по адресу am Volkspark 23 и Prinzregenstr 23
 - Conrad Keifl GmbH & Co. KG Besitzgesellschaft дом по адресу m Örlinger Holz 14, 89075 Ulm с плесенью

## Защита прав квартиросъемщиков
Для помощи квартиросъемщикам могут быть полезны **Mietervereine** (Клуб или Ассоциация арендаторов). Это специализированные организации, вступив в которые можно получать юридические консультации и помощь в различных вопросах, таких как письменная коммуникация с владельцами недвижимости, разрешение конфликтов и т.д.
Пример для южного Гессена: [Mieterbund Darmstadt](https://www.mieterbund-darmstadt.de/ru/).
Первый год членства стоит 110 Евро, последующие - 75 Евро. Говорят по английски.

## Цены по районам

* ![](files/be.png)
  * [Карта сравнения цен](https://www.immobilienscout24.de/neubau/ratgeber/aktuelle-neubau-themen/kauf-map-berlin-2016.html) по состоянию на 2016 год. Источник: [статья](http://www.immobilienscout24.de/immobilienbewertung/ratgeber/mietpreise-und-kaufpreise/mietspiegel/miet-map-berlin.html) на [immobilienscout24](http://www.immobilienscout24.de/)
  * [Интерактивная карта стоимости аренды от Berliner Morgenpost](http://interaktiv.morgenpost.de/mietkarte-berlin/). Можно выбрать площадь квартиры и ежемесячный доход, карта покажет, в каких районах скорее всего есть подходящие цены. На данный момент актуальность данных: 1-й квартал 2015.
* ![](files/mu.png)
  * Аналогичная [статья](https://www.immobilienscout24.de/immobilienbewertung/ratgeber/mietpreise-und-kaufpreise/mietspiegel/mietspiegel-muenchen.html)

Если вы не знаете, где снять:
  - Купите билет на день/неделю и покатайтесь пару дней автобусами/трамваями, посмотрите город.
  - Используйте Google Maps Street View и анализируйте улицу/район по внешнему виду (наличие/отсутствие граффити, озеленение, и т.д).

## Переезд, доставка грузов, перенос тяжестей
- http://markmanwithavan.tumblr.com/
- По Берлину:
  - +49 176 40246446: Николай (Микола, украино-русско-немецко-язычный) - владеет несколькими автомобилями разных размеров.
  - +49 176 23154660: Михаил (русскоязычный).
  - +49 176 43347280: Вадим (русскоязычный).
- Перевоз грузов из Днепропетровска (Украина): +380 562325842, +380 562367980.

## Ссылки на отчеты о поисках
- http://berlinsale.com/kak-snyat-kvartiru-v-berline-lichnoe-mnenie инструкция по поиску квартир "offline"
- http://telegra.ph/Kak-snyat-kvartiru-v-Berline-07-31 - Как снять квартиру в Берлине и не облажаться


package org.tasks.ui

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.todoroo.astrid.api.Filter
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.databinding.FragmentTaskEditEmptyBinding
import org.tasks.themes.ColorProvider
import org.tasks.themes.ThemeColor
import javax.inject.Inject

@AndroidEntryPoint
class EmptyTaskEditFragment : Fragment() {

    @Inject lateinit var themeColor: ThemeColor
    @Inject lateinit var colorProvider: ColorProvider

    companion object {
        const val EXTRA_FILTER = "extra_filter"

        fun newEmptyTaskEditFragment(filter: Filter): EmptyTaskEditFragment {
            val arguments = Bundle()
            arguments.putParcelable(EXTRA_FILTER, filter)
            val fragment = EmptyTaskEditFragment()
            fragment.arguments = arguments
            return fragment
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val binding = FragmentTaskEditEmptyBinding.inflate(inflater)

        val tint = arguments?.getParcelable<Filter>(EXTRA_FILTER)?.tint

        val color = colorProvider.getThemeColor(if (tint == null || tint == 0) {
            themeColor.primaryColor
        } else {
            tint
        })

        color.apply(binding.toolbar.toolbar)

        return binding.root
    }
}


from __future__ import unicode_literals
from datetime import datetime
from django.test import TestCase
from django_ajax.encoder import LazyJSONEncoder
import json

class LazyJSONEncoderMixinTestCase(TestCase):
    def test_default_date(self):
        data = {'datetime': datetime.today()}
        self.assertEqual('{"datetime": "' + data['datetime'].isoformat() + '"}', json.dumps(data, cls=LazyJSONEncoder))

class BaseTestCase(TestCase):
    def post(self, uri, data=None):
        response = resp = self.client.get(uri, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertEquals(200, resp.status_code)
        self.assertEquals('application/json', response['Content-Type'])
        if isinstance(response.content, str):
            return response, json.loads(response.content)
        else:
            return response, json.loads(response.content.decode('utf-8'))

class FooTestCase(BaseTestCase):
    def test_json_response(self):
        resp, data = self.post('/ajax/foo')

        self.assertEqual('OK', data['statusText'])
        self.assertEqual({'foo': True}, data['content'])

class LoginRequiredTestCase(BaseTestCase):
    def test_json_response(self):
        resp, data = self.post('/ajax/login-required')

        self.assertEquals(302, data['status'])
        self.assertEqual('FOUND', data['statusText'])

class RenderTestCase(BaseTestCase):
    def test_json_response(self):
        resp, data = self.post('/ajax/render')

        self.assertEquals(200, data['status'])
        self.assertEqual('OK', data['statusText'])
        self.assertEqual('Hello', data['content'].strip())

class RenderClassBasedViewTestCase(BaseTestCase):
    def test_json_response(self):
        resp, data = self.post('/ajax/render-class-based-view')

        self.assertEquals(200, data['status'])
        self.assertEqual('OK', data['statusText'])
        self.assertEqual('Hello', data['content'].strip())

class ExceptionTestCase(BaseTestCase):
    def test_json_response(self):
        resp, data = self.post('/ajax/exception')

        # self.assertEquals(200, data['status'])
        self.assertEqual('INTERNAL SERVER ERROR', data['statusText'])

class RaiseExceptionTestCase(BaseTestCase):
    def test_json_response(self):
        resp, data = self.post('/ajax/raise-exception')

        # self.assertEquals(200, data['status'])
        self.assertEqual('NOT FOUND', data['statusText'])


// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using RabbitMQ.Client;
using System;

namespace Microsoft.Extensions.Messaging.RabbitMQ
{
    public interface IPersistentRabbitMQConnection : IDisposable
    {
        bool IsConnected { get; }

        bool TryConnect();

        IModel CreateModel();
    }
}